@boopkit/cap-users 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -5
- package/capability.json +27 -5
- package/dist/auth-middleware.d.ts +1 -0
- package/dist/auth-middleware.d.ts.map +1 -1
- package/dist/auth-middleware.js +66 -2
- package/dist/auth-middleware.js.map +1 -1
- package/dist/auth-service.d.ts +8 -0
- package/dist/auth-service.d.ts.map +1 -1
- package/dist/auth-service.js +161 -45
- package/dist/auth-service.js.map +1 -1
- package/dist/credential-migration.d.ts +76 -0
- package/dist/credential-migration.d.ts.map +1 -0
- package/dist/credential-migration.js +126 -0
- package/dist/credential-migration.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/info-schema-tags.d.ts +21 -0
- package/dist/info-schema-tags.d.ts.map +1 -0
- package/dist/info-schema-tags.js +24 -0
- package/dist/info-schema-tags.js.map +1 -0
- package/dist/team-invitation-service.d.ts.map +1 -1
- package/dist/team-invitation-service.js +42 -8
- package/dist/team-invitation-service.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/scaffold/reference/user-auth.ref.md +49 -7
- package/scaffold/src/controllers/auth.ts +12 -16
- package/scaffold/src/middleware/auth.ts +17 -8
- package/scaffold/src/scripts/migrate-credentials.ts +69 -0
- package/scaffold/src/types/express.ts +77 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Copied into your project on install:
|
|
|
14
14
|
- `prisma/schema/accounts.prisma` — `Account`, `AccountUser`, `AccountProperty` models
|
|
15
15
|
- `src/services/users.service.ts` — wires `userService`, `identityService`, `userPropertyService`, `authService` to Prisma
|
|
16
16
|
- `src/services/accounts.service.ts` — wires `accountService`, `accountPropertyService`, `accountManagementService`, `teamInvitationService`
|
|
17
|
-
- `src/middleware/auth.ts` — `resolveVisitor`, `resolveBearer`, `addAuthContext`, `requireAuth` middleware instances
|
|
17
|
+
- `src/middleware/auth.ts` — `resolveVisitor`, `resolveBearer`, `addAuthContext`, `requireAuth`, `requireApiKey` middleware instances
|
|
18
18
|
- `src/controllers/auth.ts` — signup, login, logout, forget-device controllers
|
|
19
19
|
- `src/routes/auth.ts` — Express router mounting `/signup`, `/login`, `/logout`, `/logout/forget`
|
|
20
20
|
- `views/signup.hbs`, `views/login.hbs`, `views/partials/auth-nav.hbs` — auth views and nav partial
|
|
@@ -118,24 +118,42 @@ Three outcomes, and the middle one is the point:
|
|
|
118
118
|
|
|
119
119
|
- **No `Authorization` header, or another scheme** — passes through with `req.user` untouched, so the same router can still serve anonymous traffic. Gating remains `requireAuth`'s job.
|
|
120
120
|
- **A Bearer token that does not resolve** — `401 { error: 'invalid_token' }` with a `WWW-Authenticate: Bearer` challenge, rather than silent anonymity. A caller that presented a credential and got it wrong should be told, and it does **not** fall back to the session cookie: revoking a key has to stop the request even from a browser that still holds a session.
|
|
121
|
-
- **A Bearer token that resolves** — `req.user` is set, and everything downstream works without knowing which door the request came in.
|
|
121
|
+
- **A Bearer token that resolves** — `req.user` is set (plus `req.sessionToken`, the presented key, and `req.authStrategy = 'api-key'`), and everything downstream works without knowing which door the request came in. `requireAuth` reads both `req.user` and `req.sessionToken`, so setting the token is what lets the pairing above admit a valid key rather than redirect it to `/login`.
|
|
122
|
+
|
|
123
|
+
**Requiring a key, not just any login.** The route above is door-agnostic: a browser with a valid session cookie satisfies `requireAuth` there without presenting a key, because `resolveVisitor` runs app-wide and populates `req.user` before the API router. For a route that must be reached *with an API key* — so that revoking or expiring the key stops it even for a user whose browser session is still alive — gate with `requireApiKey` instead:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// src/routes/api.ts
|
|
127
|
+
import { resolveBearer, requireApiKey } from '../middleware/auth';
|
|
128
|
+
|
|
129
|
+
router.use(resolveBearer);
|
|
130
|
+
router.get('/integrations', requireApiKey(), api.integrations); // an API key, not a cookie
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`requireApiKey` accepts the request only when `resolveBearer` resolved it from a key (`req.authStrategy === 'api-key'`), then runs the same `isAuthorized` grant check `requireAuth` does — so an expired per-key grant is refused here even though the key still resolves.
|
|
122
134
|
|
|
123
135
|
Issue keys with `authService.createApiKey(userId)`, which returns the raw key once. Only `hashToken(apiKey)` is stored in `user_identities`, the same treatment a session cookie gets, so a read of that table hands nobody a working credential. Keys issued before this change were stored raw and will not resolve after it; reissue them.
|
|
124
136
|
|
|
137
|
+
There is deliberately no route, screen, or command for minting a key. Handing out a credential that acts as a user is not a generic operation — who may do it, for whom, whether it needs a second factor or an approval, and what the audit trail looks like are answers only your application has, and a default affordance would answer them for you on the sensitive side. Wire `createApiKey` behind whatever authorization you already trust, and tell the holder at the moment you show it that the key cannot be retrieved again.
|
|
138
|
+
|
|
125
139
|
**2.** Read the current user in a controller:
|
|
126
140
|
|
|
127
141
|
```ts
|
|
128
142
|
// src/controllers/pages.ts
|
|
129
143
|
import type { Request, Response } from 'express';
|
|
130
|
-
import type { User, BaseUserInfo } from '@boopkit/cap-users';
|
|
131
144
|
|
|
132
145
|
export function dashboard(req: Request, res: Response): void {
|
|
133
|
-
|
|
146
|
+
// req.user is typed — no cast. cap-users ships an Express request
|
|
147
|
+
// augmentation (src/types/express.ts) that declares req.user,
|
|
148
|
+
// req.sessionToken, req.visitorToken and req.authStrategy, so every handler
|
|
149
|
+
// sees the real User shape.
|
|
150
|
+
const user = req.user;
|
|
151
|
+
if (!user) { res.redirect('/login'); return; }
|
|
134
152
|
res.render('dashboard', { email: user.info.email });
|
|
135
153
|
}
|
|
136
154
|
```
|
|
137
155
|
|
|
138
|
-
`req.user` is set by `resolveVisitor` on every request. For views, `res.locals.auth` is populated by `addAuthContext` with `trustTier`, `userName`, `userEmail`, and `userInitial` — the `auth-nav` partial uses these to show the right sign-in / sign-out affordances.
|
|
156
|
+
`req.user` is set by `resolveVisitor` on every request, so the augmentation types it as optional — narrow it, or put the route behind `requireAuth`, before you read it. The typing arrives with the scaffold file `src/types/express.ts`, which your tsconfig compiles along with the rest of `src/`; there is no import to add. For views, `res.locals.auth` is populated by `addAuthContext` with `trustTier`, `userName`, `userEmail`, and `userInitial` — the `auth-nav` partial uses these to show the right sign-in / sign-out affordances.
|
|
139
157
|
|
|
140
158
|
### Extending the user shape
|
|
141
159
|
|
package/capability.json
CHANGED
|
@@ -9,21 +9,23 @@
|
|
|
9
9
|
],
|
|
10
10
|
"package": "@boopkit/cap-users",
|
|
11
11
|
"dependencies": [
|
|
12
|
-
"bcryptjs",
|
|
13
|
-
"cookie-parser"
|
|
12
|
+
"bcryptjs@^3",
|
|
13
|
+
"cookie-parser@^1"
|
|
14
14
|
],
|
|
15
15
|
"devDependencies": [
|
|
16
|
-
"@types/bcryptjs",
|
|
17
|
-
"@types/cookie-parser"
|
|
16
|
+
"@types/bcryptjs@^2",
|
|
17
|
+
"@types/cookie-parser@^1"
|
|
18
18
|
],
|
|
19
19
|
"files": [
|
|
20
20
|
"prisma/schema/users.prisma",
|
|
21
21
|
"prisma/schema/accounts.prisma",
|
|
22
22
|
"src/services/users.service.ts",
|
|
23
23
|
"src/services/accounts.service.ts",
|
|
24
|
+
"src/types/express.ts",
|
|
24
25
|
"src/middleware/auth.ts",
|
|
25
26
|
"src/controllers/auth.ts",
|
|
26
27
|
"src/routes/auth.ts",
|
|
28
|
+
"src/scripts/migrate-credentials.ts",
|
|
27
29
|
"views/signup.hbs",
|
|
28
30
|
"views/login.hbs",
|
|
29
31
|
"views/partials/auth-nav.hbs",
|
|
@@ -33,6 +35,12 @@
|
|
|
33
35
|
"skills": [
|
|
34
36
|
"boop-users-user-data-modeling"
|
|
35
37
|
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"credentials:migrate": {
|
|
40
|
+
"cmd": "tsx src/scripts/migrate-credentials.ts",
|
|
41
|
+
"description": "Rehash cleartext credentials in property tables (report only; pass -- --run to apply). ONE-WAY — back up first."
|
|
42
|
+
}
|
|
43
|
+
},
|
|
36
44
|
"hooks": [
|
|
37
45
|
{
|
|
38
46
|
"phase": "imports",
|
|
@@ -92,7 +100,9 @@
|
|
|
92
100
|
"steps": [
|
|
93
101
|
"Run database migration to create user and account tables: npm run db:migrate -- --name add-${capabilityName}",
|
|
94
102
|
"Generate updated Prisma client: npm run db:generate",
|
|
95
|
-
"Add {{> auth-nav}} to your main.hbs layout — inside <body> before your main content."
|
|
103
|
+
"Add {{> auth-nav}} to your main.hbs layout — inside <body> before your main content.",
|
|
104
|
+
"Authenticate API requests with a bearer token: mount resolveBearer (exported from src/middleware/auth.ts) on your API router, then gate routes with requireAuth(). It is deliberately NOT mounted for you — app-wide it would reject every request carrying a bad token.",
|
|
105
|
+
"Issuing API keys is yours to design. authService.createApiKey(userId) mints one and returns the raw key exactly once; nothing stores a recoverable copy. boop ships no UI or CLI for this on purpose — who may mint a key, for whom, and with what approval is a decision only your application can make. See reference/user-auth.ref.md before you expose it."
|
|
96
106
|
],
|
|
97
107
|
"setupChecks": [
|
|
98
108
|
{
|
|
@@ -104,6 +114,18 @@
|
|
|
104
114
|
"type": "prisma-generate",
|
|
105
115
|
"label": "Generate Prisma client",
|
|
106
116
|
"hint": "npm run db:generate"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"type": "probe",
|
|
120
|
+
"label": "No cleartext authorization tokens",
|
|
121
|
+
"query": "SELECT 1 FROM user_properties WHERE property = 'authorization' AND info_schema <> 'authorization-hashed-01' LIMIT 1",
|
|
122
|
+
"hint": "Some authorization grants predate credential hashing and may hold a raw session token or API key. Run the credential-hashing migration (see reference/user-auth.ref.md)."
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"type": "probe",
|
|
126
|
+
"label": "No cleartext invitation tokens",
|
|
127
|
+
"query": "SELECT 1 FROM account_properties WHERE property = 'team_invitations' AND info_schema <> 'team-invitations-hashed-01' LIMIT 1",
|
|
128
|
+
"hint": "Some team invitations predate credential hashing and may hold a raw, redeemable token. Run the credential-hashing migration (see reference/user-auth.ref.md)."
|
|
107
129
|
}
|
|
108
130
|
]
|
|
109
131
|
},
|
|
@@ -58,6 +58,7 @@ export interface AuthMiddleware {
|
|
|
58
58
|
resolveBearer: (req: Request, res: Response, next: NextFunction) => void;
|
|
59
59
|
getOrCreateUser: (req: Request, res: Response) => Promise<User<BaseUserInfo>>;
|
|
60
60
|
requireAuth: (requiredTrust?: TrustTier) => (req: Request, res: Response, next: NextFunction) => void;
|
|
61
|
+
requireApiKey: (requiredTrust?: TrustTier) => (req: Request, res: Response, next: NextFunction) => void;
|
|
61
62
|
addAuthContext: (req: Request, res: Response, next: NextFunction) => void;
|
|
62
63
|
}
|
|
63
64
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-middleware.d.ts","sourceRoot":"","sources":["../src/auth-middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAMhE,uDAAuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,WAAW,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;CACxC;AAED,wCAAwC;AACxC,MAAM,WAAW,qBAAqB;IACpC,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1E,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACzE,eAAe,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAC9E,WAAW,EAAE,CAAC,aAAa,CAAC,EAAE,SAAS,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACtG,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;CAC3E;AASD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,kBAAkB,EACxB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,cAAc,
|
|
1
|
+
{"version":3,"file":"auth-middleware.d.ts","sourceRoot":"","sources":["../src/auth-middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAMhE,uDAAuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,WAAW,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;CACxC;AAED,wCAAwC;AACxC,MAAM,WAAW,qBAAqB;IACpC,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1E,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACzE,eAAe,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAC9E,WAAW,EAAE,CAAC,aAAa,CAAC,EAAE,SAAS,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACtG,aAAa,EAAE,CAAC,aAAa,CAAC,EAAE,SAAS,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxG,cAAc,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;CAC3E;AASD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,kBAAkB,EACxB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,cAAc,CAkShB"}
|
package/dist/auth-middleware.js
CHANGED
|
@@ -58,6 +58,22 @@ function createAuthMiddleware(deps, options) {
|
|
|
58
58
|
* - A Bearer token that resolves: req.user is set to the same principal the
|
|
59
59
|
* cookie path yields, so everything downstream — requireAuth, trust tiers,
|
|
60
60
|
* res.locals.auth — works without knowing which door the request came in.
|
|
61
|
+
* It also records two more fields the cookie path does not:
|
|
62
|
+
* - req.sessionToken = the presented raw key. requireAuth reads BOTH
|
|
63
|
+
* req.user and req.sessionToken and denies if either is missing, so
|
|
64
|
+
* setting only req.user would make the published `resolveBearer` +
|
|
65
|
+
* `requireAuth` pairing deny every bearer request. sessionToken is also
|
|
66
|
+
* the `identityKey` requireAuth hands isAuthorized, and createApiKey
|
|
67
|
+
* records the per-key grant under the RAW key — so the presented key is
|
|
68
|
+
* exactly what that call is asking for, and routing it through
|
|
69
|
+
* isAuthorized restores the per-key grant check ("expire the grant") for
|
|
70
|
+
* free. COUPLING: this relies on createApiKey keying the grant on the
|
|
71
|
+
* raw key (auth-service.ts, `setAuthorization(userId, apiKey, ...)`).
|
|
72
|
+
* boop-owbv proposes hashing that field; whichever of the two lands
|
|
73
|
+
* second MUST update the other or API-key authorization breaks silently.
|
|
74
|
+
* - req.authStrategy = 'api-key', so a gate that must accept ONLY a key
|
|
75
|
+
* (requireApiKey) can tell a key principal from a cookie one. req.user
|
|
76
|
+
* alone cannot: resolveVisitor may have populated it from the cookie.
|
|
61
77
|
*
|
|
62
78
|
* It deliberately does **not** fall back to the session cookie when the token
|
|
63
79
|
* is bad. One of the donor codebases the oauth work drew on did fall back on
|
|
@@ -82,7 +98,10 @@ function createAuthMiddleware(deps, options) {
|
|
|
82
98
|
authService
|
|
83
99
|
.validateApiKey(token)
|
|
84
100
|
.then((user) => {
|
|
85
|
-
|
|
101
|
+
const r = req;
|
|
102
|
+
r.user = user;
|
|
103
|
+
r.sessionToken = token;
|
|
104
|
+
r.authStrategy = 'api-key';
|
|
86
105
|
next();
|
|
87
106
|
})
|
|
88
107
|
.catch((err) => {
|
|
@@ -178,8 +197,52 @@ function createAuthMiddleware(deps, options) {
|
|
|
178
197
|
}
|
|
179
198
|
next();
|
|
180
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* requireApiKey — gate for routes that must be reached with an API key, not
|
|
202
|
+
* merely with any authenticated principal.
|
|
203
|
+
*
|
|
204
|
+
* requireAuth answers "is there an authorized principal?" — and a browser
|
|
205
|
+
* holding a session cookie is one. Because resolveVisitor is mounted app-wide
|
|
206
|
+
* (well ahead of the API router), such a browser arrives at /api/* already
|
|
207
|
+
* carrying req.user and req.sessionToken from its cookie. Gating a key-only
|
|
208
|
+
* route with requireAuth would therefore let any logged-in browser through
|
|
209
|
+
* with no key, and would let a revoked key keep working from a browser whose
|
|
210
|
+
* session is still alive — defeating the promise that a key is an independent,
|
|
211
|
+
* independently-revocable credential.
|
|
212
|
+
*
|
|
213
|
+
* The one fact that separates the two is req.authStrategy, which only
|
|
214
|
+
* resolveBearer sets. This gate requires it, then makes the SAME authorization
|
|
215
|
+
* decision requireAuth does — isAuthorized against the grant behind the
|
|
216
|
+
* presented credential — and answers with the 401 an API client expects
|
|
217
|
+
* rather than an HTML redirect.
|
|
218
|
+
*/
|
|
219
|
+
function requireApiKey(requiredTrust = 'authenticated') {
|
|
220
|
+
return function requireApiKeyMiddleware(req, res, next) {
|
|
221
|
+
const user = req.user;
|
|
222
|
+
const sessionToken = req.sessionToken;
|
|
223
|
+
const authStrategy = req.authStrategy;
|
|
224
|
+
if (!user || !sessionToken || authStrategy !== 'api-key') {
|
|
225
|
+
res.set('WWW-Authenticate', bearerChallenge).status(401).json({ error: 'Unauthorized' });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
authService
|
|
229
|
+
.isAuthorized(user.id, sessionToken, requiredTrust)
|
|
230
|
+
.then((authorized) => {
|
|
231
|
+
if (authorized)
|
|
232
|
+
return next();
|
|
233
|
+
res.set('WWW-Authenticate', bearerChallenge).status(401).json({ error: 'Unauthorized' });
|
|
234
|
+
})
|
|
235
|
+
.catch(next);
|
|
236
|
+
};
|
|
237
|
+
}
|
|
181
238
|
function isApiRequest(req) {
|
|
182
|
-
|
|
239
|
+
// Test req.originalUrl, not req.path: inside a router mounted with
|
|
240
|
+
// app.use('/api', apiRoutes), req.path is router-relative ('/me' for
|
|
241
|
+
// GET /api/me) and would miss the '/api' prefix entirely, so an
|
|
242
|
+
// unauthenticated API caller would get an HTML redirect instead of a 401.
|
|
243
|
+
// originalUrl carries the full request path (with an optional query string).
|
|
244
|
+
const pathname = req.originalUrl.split('?')[0];
|
|
245
|
+
return pathname === '/api' || pathname.startsWith('/api/');
|
|
183
246
|
}
|
|
184
247
|
function denyAccess(req, res) {
|
|
185
248
|
logger.warn('access denied', { method: req.method, path: req.originalUrl, userId: req.user?.id });
|
|
@@ -223,6 +286,7 @@ function createAuthMiddleware(deps, options) {
|
|
|
223
286
|
resolveBearer,
|
|
224
287
|
getOrCreateUser,
|
|
225
288
|
requireAuth,
|
|
289
|
+
requireApiKey,
|
|
226
290
|
addAuthContext,
|
|
227
291
|
};
|
|
228
292
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-middleware.js","sourceRoot":"","sources":["../src/auth-middleware.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;
|
|
1
|
+
{"version":3,"file":"auth-middleware.js","sourceRoot":"","sources":["../src/auth-middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/auth-service.d.ts
CHANGED
|
@@ -104,6 +104,14 @@ export declare function generateToken(partition?: string): string;
|
|
|
104
104
|
* so a fast hash is sufficient (no need for bcrypt).
|
|
105
105
|
*/
|
|
106
106
|
export declare function hashToken(token: string): string;
|
|
107
|
+
/**
|
|
108
|
+
* True when `value` is already a SHA-256 hex digest, so hashing it again would
|
|
109
|
+
* double-hash. Session tokens (dashed base36, ~29 chars) and API keys (43
|
|
110
|
+
* base64url chars) never match this shape, so it distinguishes a raw credential
|
|
111
|
+
* from its stored hash by value shape alone. This is the idempotency test the
|
|
112
|
+
* migration tool and the auth service share.
|
|
113
|
+
*/
|
|
114
|
+
export declare function isHashedToken(value: string): boolean;
|
|
107
115
|
/**
|
|
108
116
|
* Create an auth business service orchestrating signup, login, and
|
|
109
117
|
* session management on top of user data services.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-service.d.ts","sourceRoot":"","sources":["../src/auth-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth-service.d.ts","sourceRoot":"","sources":["../src/auth-service.ts"],"names":[],"mappings":"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"}
|