@owlmeans/server-auth-token 0.1.18-rc.31 → 0.1.18-rc.33
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 +1 -1
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-auth-token/SKILL.md +6 -6
- package/build/guard.d.ts +19 -3
- package/build/guard.d.ts.map +1 -1
- package/build/guard.js +19 -20
- package/build/guard.js.map +1 -1
- package/build/handlers/create.d.ts +5 -0
- package/build/handlers/create.d.ts.map +1 -0
- package/build/handlers/create.js +18 -0
- package/build/handlers/create.js.map +1 -0
- package/build/handlers/index.d.ts +8 -35
- package/build/handlers/index.d.ts.map +1 -1
- package/build/handlers/index.js +8 -109
- package/build/handlers/index.js.map +1 -1
- package/build/handlers/issue.d.ts +6 -0
- package/build/handlers/issue.d.ts.map +1 -0
- package/build/handlers/issue.js +42 -0
- package/build/handlers/issue.js.map +1 -0
- package/build/handlers/list.d.ts +3 -0
- package/build/handlers/list.d.ts.map +1 -0
- package/build/handlers/list.js +18 -0
- package/build/handlers/list.js.map +1 -0
- package/build/handlers/refuse.d.ts +19 -0
- package/build/handlers/refuse.d.ts.map +1 -0
- package/build/handlers/refuse.js +20 -0
- package/build/handlers/refuse.js.map +1 -0
- package/build/handlers/revoke.d.ts +7 -0
- package/build/handlers/revoke.d.ts.map +1 -0
- package/build/handlers/revoke.js +25 -0
- package/build/handlers/revoke.js.map +1 -0
- package/build/handlers/types.d.ts +11 -1
- package/build/handlers/types.d.ts.map +1 -1
- package/build/handlers/utils/types.d.ts +10 -0
- package/build/handlers/utils/types.d.ts.map +1 -0
- package/build/handlers/utils/types.js +2 -0
- package/build/handlers/utils/types.js.map +1 -0
- package/build/handlers/utils.d.ts +4 -0
- package/build/handlers/utils.d.ts.map +1 -0
- package/build/handlers/utils.js +11 -0
- package/build/handlers/utils.js.map +1 -0
- package/build/hash/types.d.ts +16 -0
- package/build/hash/types.d.ts.map +1 -0
- package/build/hash/types.js +2 -0
- package/build/hash/types.js.map +1 -0
- package/build/hash.d.ts +4 -11
- package/build/hash.d.ts.map +1 -1
- package/build/hash.js +11 -14
- package/build/hash.js.map +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/prefix.d.ts +4 -0
- package/build/prefix.d.ts.map +1 -0
- package/build/prefix.js +4 -0
- package/build/prefix.js.map +1 -0
- package/package.json +12 -12
- package/src/guard.ts +21 -24
- package/src/handlers/create.ts +21 -0
- package/src/handlers/index.ts +11 -125
- package/src/handlers/issue.ts +50 -0
- package/src/handlers/list.ts +21 -0
- package/src/handlers/refuse.ts +20 -0
- package/src/handlers/revoke.ts +30 -0
- package/src/handlers/types.ts +12 -1
- package/src/handlers/utils/types.ts +10 -0
- package/src/handlers/utils.ts +18 -0
- package/src/hash/types.ts +16 -0
- package/src/hash.ts +15 -14
- package/src/index.ts +2 -0
- package/src/prefix.ts +6 -0
- package/tests/guard.spec.ts +14 -14
- package/tests/handlers.spec.ts +13 -13
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
6
6
|
your project's skill store (`.agents/skills/`):
|
|
7
7
|
|
|
8
8
|
```sh
|
|
9
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
9
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.51
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/server-auth-token",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-10-
|
|
4
|
+
"version": "0.1.18-rc.33",
|
|
5
|
+
"generatedAt": "2026-10-06T11:45:57.687Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: server-auth-token
|
|
3
|
-
description: How to use @owlmeans/server-auth-token — the server half of long-lived access tokens — the Mongo store, the guard that verifies a presented token and intersects its scopes with the profile's, the mint/list/revoke handlers, the shared `issueAccessToken`, audience admission for OAuth-issued tokens, and the coguard that admits a token on every already-guarded route. Auto-invoked when registering the token guard or resources, mounting the token handlers, or diagnosing a 401 on a route an access token should reach.
|
|
3
|
+
description: How to use @owlmeans/server-auth-token — the server half of long-lived access tokens — the Mongo store, the guard that verifies a presented token and intersects its scopes with the profile's, the mint/list/revoke handlers, the shared `accessTokenIssuerOf(ctx).issueAccessToken`, audience admission for OAuth-issued tokens, and the coguard that admits a token on every already-guarded route. Auto-invoked when registering the token guard or resources, mounting the token handlers, or diagnosing a 401 on a route an access token should reach.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/server-auth-token
|
|
9
9
|
|
|
10
10
|
**Layer:** Server
|
|
11
|
-
**Install:** `"@owlmeans/server-auth-token": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/server-auth-token": "^0.1.18-rc.33"` in `dependencies`
|
|
12
12
|
**Contracts:** `@owlmeans/auth-token` — the record, the routes, the format helpers
|
|
13
13
|
|
|
14
14
|
## Key Exports
|
|
@@ -20,10 +20,10 @@ user-invocable: false
|
|
|
20
20
|
| `makeAuthTokenGuard(alias?, opts?)` | The guard itself |
|
|
21
21
|
| `makeAccessTokenResource(dbAlias?)` | The Mongo resource, with its three indexes |
|
|
22
22
|
| `prefixOf(context, opts?)` | The deployment's prefix: guard options → config → default |
|
|
23
|
-
| `hashAccessToken(token)` ·
|
|
23
|
+
| `tokenHashHelper` — `.hashAccessToken(token)` · `.mintAccessToken(prefix)` | The stored form; one minted `{ token, hash, display }` |
|
|
24
24
|
| `withAuthTokenCoguard(protocolTree, guard?)` | Return the same-shaped immutable tree with the guard appended to every already-guarded protocol |
|
|
25
25
|
| `listAccessTokens` · `createAccessToken` · `revokeAccessToken` | The three handlers |
|
|
26
|
-
| `issueAccessToken(
|
|
26
|
+
| `accessTokenIssuerOf(ctx).issueAccessToken(subject, request)` | Mint one token for `{ entityId, userId, profileId, role, scopes }` — what `createAccessToken` does over an HTTP body, and what an OAuth token endpoint calls over a session it verified itself. `request` is `CreateAccessToken` plus `audience?: string[]` |
|
|
27
27
|
| `refuseTokenAuth(req, what)` | The one "interactive session only" check — throws `AuthForbidden` when `req.auth.type` is an access token |
|
|
28
28
|
| `AuthTokenGuardOptions` (`prefix`, `denyAliases`, `touchInterval`, `resourceAlias`, `profileAlias`, `resources`) · `IssueAccessTokenSubject` · `IssueAccessTokenRequest` · `AuthTokenConfig` · `AccessTokenResource` | Types |
|
|
29
29
|
|
|
@@ -133,7 +133,7 @@ works.
|
|
|
133
133
|
## Tests
|
|
134
134
|
|
|
135
135
|
`bun test ./tests` in the package — what the guard claims, what it resolves (including that a token
|
|
136
|
-
can never outrank its profile), the mint/list/revoke rules, `issueAccessToken` parity with the route, audience admission, and the coguard's four properties.
|
|
136
|
+
can never outrank its profile), the mint/list/revoke rules, `accessTokenIssuerOf(ctx).issueAccessToken` parity with the route, audience admission, and the coguard's four properties.
|
|
137
137
|
|
|
138
138
|
## Depends On
|
|
139
139
|
|
|
@@ -145,5 +145,5 @@ can never outrank its profile), the mint/list/revoke rules, `issueAccessToken` p
|
|
|
145
145
|
## Related
|
|
146
146
|
|
|
147
147
|
- [[auth-token]] — the format, the routes, `audience` and the client carrier guard
|
|
148
|
-
- [[server-oauth]] — the authorization server that mints audience-scoped tokens through `issueAccessToken`
|
|
148
|
+
- [[server-oauth]] — the authorization server that mints audience-scoped tokens through `accessTokenIssuerOf(ctx).issueAccessToken`
|
|
149
149
|
- [[web-auth-token]] — the management panel · [[auth-protocol]] · [[server-auth]]
|
package/build/guard.d.ts
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import type { GuardService } from '@owlmeans/entrypoint';
|
|
2
|
-
import type {
|
|
3
|
-
/**
|
|
4
|
-
|
|
2
|
+
import type { AuthTokenGuardOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The guard that verifies a long-lived access token.
|
|
5
|
+
*
|
|
6
|
+
* It is a GUARD, not an authentication method: nothing is exchanged, no envelope is signed, no
|
|
7
|
+
* session begins. A caller presents a credential it was handed once, and the guard turns it into
|
|
8
|
+
* the same `Auth` payload every other guard produces — so ownership gates, entitlement gates and
|
|
9
|
+
* handlers written against a browser session work unchanged for an API client.
|
|
10
|
+
*
|
|
11
|
+
* Three properties are what keep it from colliding with the guards it sits beside:
|
|
12
|
+
*
|
|
13
|
+
* - it claims a header only when the value carries the deployment's prefix, so an OwlMeans
|
|
14
|
+
* `ED25519-BASIC-TOKEN` bearer, an OIDC one, and this one can all arrive under `Authorization`
|
|
15
|
+
* without any of them shadowing the others;
|
|
16
|
+
* - it accepts the OwlMeans `AUTH-TOKEN` scheme AND plain `Bearer`, because a third-party client
|
|
17
|
+
* configured with a URL sends the latter whatever the documentation says;
|
|
18
|
+
* - it intersects the token's scopes with its profile's on every request, so a token can never
|
|
19
|
+
* outlive or outrank the person who minted it.
|
|
20
|
+
*/
|
|
5
21
|
export declare const makeAuthTokenGuard: (alias?: string, opts?: AuthTokenGuardOptions) => GuardService;
|
|
6
22
|
//# sourceMappingURL=guard.d.ts.map
|
package/build/guard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAqC,YAAY,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAqC,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAK3F,OAAO,KAAK,EAA0D,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAK/G;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,WACtB,MAAM,SAA2B,qBAAqB,KAC5D,YA6HF,CAAA"}
|
package/build/guard.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ALL_SCOPES, AuthroizationType } from '@owlmeans/auth';
|
|
2
|
-
import {
|
|
2
|
+
import { AUTH_TOKEN_SCHEME, AUTH_TOKEN_RESOURCE, AUTH_TOKEN_TOUCH_INTERVAL, BEARER_SCHEME, GUARD_AUTH_TOKEN, tokenFormatHelper } from '@owlmeans/auth-token';
|
|
3
3
|
import { AUTH_HEADER } from '@owlmeans/auth';
|
|
4
4
|
import { ENTITY_RESOLVER } from '@owlmeans/auth-common';
|
|
5
5
|
import { createService } from '@owlmeans/context';
|
|
6
6
|
import { AUTH_IDENTITY_PROFILE } from '@owlmeans/server-auth-identity';
|
|
7
|
-
import {
|
|
7
|
+
import { tokenHashHelper } from './hash.js';
|
|
8
|
+
import { prefixOf } from './prefix.js';
|
|
8
9
|
import { logger, logThrottle } from '@owlmeans/log';
|
|
9
|
-
|
|
10
|
-
export const prefixOf = (context, opts) => opts?.prefix ?? context.cfg.authToken?.prefix ?? AUTH_TOKEN_DEFAULT_PREFIX;
|
|
10
|
+
const log = logger('server-auth-token');
|
|
11
11
|
/**
|
|
12
12
|
* The guard that verifies a long-lived access token.
|
|
13
13
|
*
|
|
@@ -26,28 +26,27 @@ export const prefixOf = (context, opts) => opts?.prefix ?? context.cfg.authToken
|
|
|
26
26
|
* - it intersects the token's scopes with its profile's on every request, so a token can never
|
|
27
27
|
* outlive or outrank the person who minted it.
|
|
28
28
|
*/
|
|
29
|
-
const log = logger('server-auth-token');
|
|
30
|
-
/**
|
|
31
|
-
* A presented access token the guard did not accept. Never the token, not even its display form:
|
|
32
|
-
* its record id only. Once a minute per token and reason — a client retrying a revoked token calls every second.
|
|
33
|
-
*/
|
|
34
|
-
const refuse = (reason, record) => {
|
|
35
|
-
if (logThrottle(`auth-token:${reason}:${record?.id ?? ''}`, 60_000)) {
|
|
36
|
-
log.warn('Access token refused', {
|
|
37
|
-
reason, tokenId: record?.id, entityId: record?.entityId,
|
|
38
|
-
}, { event: 'auth.refused' });
|
|
39
|
-
}
|
|
40
|
-
return false;
|
|
41
|
-
};
|
|
42
29
|
export const makeAuthTokenGuard = (alias = GUARD_AUTH_TOKEN, opts = {}) => {
|
|
30
|
+
/**
|
|
31
|
+
* A presented access token the guard did not accept. Never the token, not even its display form:
|
|
32
|
+
* its record id only. Once a minute per token and reason — a client retrying a revoked token calls every second.
|
|
33
|
+
*/
|
|
34
|
+
const refuse = (reason, record) => {
|
|
35
|
+
if (logThrottle(`auth-token:${reason}:${record?.id ?? ''}`, 60_000)) {
|
|
36
|
+
log.warn('Access token refused', {
|
|
37
|
+
reason, tokenId: record?.id, entityId: record?.entityId,
|
|
38
|
+
}, { event: 'auth.refused' });
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
};
|
|
43
42
|
const touchInterval = opts.touchInterval ?? AUTH_TOKEN_TOUCH_INTERVAL;
|
|
44
43
|
const present = (req, prefix) => {
|
|
45
|
-
const parsed = parseAuthorizationHeader(req.headers?.[AUTH_HEADER]);
|
|
44
|
+
const parsed = tokenFormatHelper.parseAuthorizationHeader(req.headers?.[AUTH_HEADER]);
|
|
46
45
|
if (parsed == null)
|
|
47
46
|
return null;
|
|
48
47
|
if (parsed.scheme !== AUTH_TOKEN_SCHEME && parsed.scheme !== BEARER_SCHEME)
|
|
49
48
|
return null;
|
|
50
|
-
return isAccessToken(parsed.value, prefix) ? parsed.value : null;
|
|
49
|
+
return tokenFormatHelper.isAccessToken(parsed.value, prefix) ? parsed.value : null;
|
|
51
50
|
};
|
|
52
51
|
const service = createService(alias, {
|
|
53
52
|
match: async (req) => {
|
|
@@ -70,7 +69,7 @@ export const makeAuthTokenGuard = (alias = GUARD_AUTH_TOKEN, opts = {}) => {
|
|
|
70
69
|
if (presented == null)
|
|
71
70
|
return false;
|
|
72
71
|
const tokens = context.resource(opts.resourceAlias ?? AUTH_TOKEN_RESOURCE);
|
|
73
|
-
const record = await tokens.load({ hash: hashAccessToken(presented) });
|
|
72
|
+
const record = await tokens.load({ hash: tokenHashHelper.hashAccessToken(presented) });
|
|
74
73
|
if (record == null)
|
|
75
74
|
return refuse('unknown');
|
|
76
75
|
if (record.revokedAt != null)
|
package/build/guard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAC9D,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AuthTokenEntrypoints } from '@owlmeans/auth-token';
|
|
2
|
+
export declare const createAccessToken: (protocol: AuthTokenEntrypoints['create']) => import("@owlmeans/server-entrypoint").BoundEntrypointHandler<import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
3
|
+
body: import("@owlmeans/auth-token").CreateAccessToken;
|
|
4
|
+
}, import("@owlmeans/auth-token").IssuedAccessToken>>;
|
|
5
|
+
//# sourceMappingURL=create.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/handlers/create.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAOhE,eAAO,MAAM,iBAAiB,aAAc,oBAAoB,CAAC,QAAQ,CAAC;;qDAYxE,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AuthForbidden, AuthRole } from '@owlmeans/auth';
|
|
2
|
+
import { makeEntityScope } from '@owlmeans/auth-common';
|
|
3
|
+
import { handlers } from '@owlmeans/server-api';
|
|
4
|
+
import { accessTokenIssuerOf } from './issue.js';
|
|
5
|
+
import { refuseTokenAuth } from './refuse.js';
|
|
6
|
+
export const createAccessToken = (protocol) => handlers().body(protocol, async (payload, context, req) => {
|
|
7
|
+
const ctx = context;
|
|
8
|
+
refuseTokenAuth(req, 'token-mint');
|
|
9
|
+
const entityId = makeEntityScope(req).requireEntityKey();
|
|
10
|
+
const profileId = req.auth?.profileId;
|
|
11
|
+
const userId = req.auth?.userId;
|
|
12
|
+
if (profileId == null || userId == null)
|
|
13
|
+
throw new AuthForbidden('profile');
|
|
14
|
+
return accessTokenIssuerOf(ctx).issueAccessToken({
|
|
15
|
+
entityId, profileId, userId, role: req.auth?.role ?? AuthRole.User, scopes: req.auth?.scopes ?? [],
|
|
16
|
+
}, payload);
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/handlers/create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAAwC,EAAE,EAAE,CAAC,QAAQ,EAAoB,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;IACzJ,MAAM,GAAG,GAAG,OAA2B,CAAA;IACvC,eAAe,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;IAElC,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,gBAAgB,EAAE,CAAA;IACxD,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,CAAA;IACrC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,MAAM,CAAA;IAC/B,IAAI,SAAS,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI;QAAE,MAAM,IAAI,aAAa,CAAC,SAAS,CAAC,CAAA;IAE3E,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC,gBAAgB,CAAC;QAC/C,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE;KACnG,EAAE,OAAO,CAAC,CAAA;AACb,CAAC,CAAC,CAAA"}
|
|
@@ -1,39 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { IssuedAccessToken } from '@owlmeans/auth-token';
|
|
2
2
|
import type { AuthTokenContext } from '../types.js';
|
|
3
3
|
import type { IssueAccessTokenRequest, IssueAccessTokenSubject } from './types.js';
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* therefore require a credential a person produced in a browser — which is also why the deployment
|
|
10
|
-
* names these routes in the guard's deny list, so the refusal is a 401 at the boundary rather than
|
|
11
|
-
* a check every future handler has to remember.
|
|
12
|
-
*
|
|
13
|
-
* Exported because an OAuth authorization server's consent-approval handler
|
|
14
|
-
* (`@owlmeans/server-oauth`) mints tokens too and needs the exact same guard, over a session it
|
|
15
|
-
* verified itself rather than a route this package bound.
|
|
16
|
-
*/
|
|
17
|
-
export declare const refuseTokenAuth: (req: {
|
|
18
|
-
auth?: {
|
|
19
|
-
type?: string;
|
|
20
|
-
};
|
|
21
|
-
}, what: string) => void;
|
|
22
|
-
/**
|
|
23
|
-
* Mint one token for `subject`, exactly what `createAccessToken` does over an HTTP body — pulled
|
|
24
|
-
* out so a second caller (an OAuth token endpoint) can mint the SAME shape of record over a
|
|
25
|
-
* session it authenticated its own way, without a duplicate copy of the narrowing rule or the
|
|
26
|
-
* TTL clamp silently drifting from this one.
|
|
27
|
-
*/
|
|
28
|
-
export declare const issueAccessToken: (ctx: AuthTokenContext, subject: IssueAccessTokenSubject, payload: IssueAccessTokenRequest) => Promise<IssuedAccessToken>;
|
|
29
|
-
export declare const listAccessTokens: (protocol: AuthTokenEntrypoints['list']) => import("@owlmeans/server-entrypoint").BoundEntrypointHandler<import("@owlmeans/entrypoint").EntrypointProtocol<import("@owlmeans/entrypoint").OpenRequest, AccessTokenList>>;
|
|
30
|
-
export declare const createAccessToken: (protocol: AuthTokenEntrypoints['create']) => import("@owlmeans/server-entrypoint").BoundEntrypointHandler<import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
31
|
-
body: import("@owlmeans/auth-token").CreateAccessToken;
|
|
32
|
-
}, IssuedAccessToken>>;
|
|
33
|
-
export declare const revokeAccessToken: (protocol: AuthTokenEntrypoints['revoke']) => import("@owlmeans/server-entrypoint").BoundEntrypointHandler<import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
34
|
-
params: import("@owlmeans/auth-token").AccessTokenParams;
|
|
35
|
-
}, {
|
|
36
|
-
id: string;
|
|
37
|
-
}>>;
|
|
4
|
+
export * from './refuse.js';
|
|
5
|
+
export * from './issue.js';
|
|
6
|
+
export * from './list.js';
|
|
7
|
+
export * from './create.js';
|
|
8
|
+
export * from './revoke.js';
|
|
38
9
|
export type { IssueAccessTokenRequest, IssueAccessTokenSubject } from './types.js';
|
|
10
|
+
/** @deprecated compat:factory-refactor — use `accessTokenIssuerOf(ctx).issueAccessToken(…)` */
|
|
11
|
+
export declare const issueAccessToken: (ctx: AuthTokenContext, subject: IssueAccessTokenSubject, payload: IssueAccessTokenRequest) => Promise<IssuedAccessToken>;
|
|
39
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAElF,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAE3B,YAAY,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAElF,+FAA+F;AAC/F,eAAO,MAAM,gBAAgB,QACtB,gBAAgB,WAAW,uBAAuB,WAAW,uBAAuB,KACxF,OAAO,CAAC,iBAAiB,CAAsE,CAAA"}
|
package/build/handlers/index.js
CHANGED
|
@@ -1,110 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return rest;
|
|
10
|
-
};
|
|
11
|
-
const tokens = (ctx) => ctx.resource(AUTH_TOKEN_RESOURCE);
|
|
12
|
-
/**
|
|
13
|
-
* A token may never mint another token.
|
|
14
|
-
*
|
|
15
|
-
* Minting is the one operation that turns a stolen credential into a permanent one: a token that
|
|
16
|
-
* can create tokens survives the revocation of the token that leaked. Creating and revoking
|
|
17
|
-
* therefore require a credential a person produced in a browser — which is also why the deployment
|
|
18
|
-
* names these routes in the guard's deny list, so the refusal is a 401 at the boundary rather than
|
|
19
|
-
* a check every future handler has to remember.
|
|
20
|
-
*
|
|
21
|
-
* Exported because an OAuth authorization server's consent-approval handler
|
|
22
|
-
* (`@owlmeans/server-oauth`) mints tokens too and needs the exact same guard, over a session it
|
|
23
|
-
* verified itself rather than a route this package bound.
|
|
24
|
-
*/
|
|
25
|
-
export const refuseTokenAuth = (req, what) => {
|
|
26
|
-
if (req.auth?.type === AuthroizationType.AuthToken) {
|
|
27
|
-
throw new AuthForbidden(what);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* Mint one token for `subject`, exactly what `createAccessToken` does over an HTTP body — pulled
|
|
32
|
-
* out so a second caller (an OAuth token endpoint) can mint the SAME shape of record over a
|
|
33
|
-
* session it authenticated its own way, without a duplicate copy of the narrowing rule or the
|
|
34
|
-
* TTL clamp silently drifting from this one.
|
|
35
|
-
*/
|
|
36
|
-
export const issueAccessToken = async (ctx, subject, payload) => {
|
|
37
|
-
const own = subject.scopes;
|
|
38
|
-
const scopes = payload.scopes ?? own;
|
|
39
|
-
// A token is a delegation, so it can only ever narrow. Asking for a scope the caller does not
|
|
40
|
-
// hold is refused rather than silently dropped: a token that quietly grants less than it was
|
|
41
|
-
// asked for fails later, somewhere else, with an error about the wrong thing.
|
|
42
|
-
if (!own.includes(ALL_SCOPES) && scopes.some(scope => !own.includes(scope))) {
|
|
43
|
-
throw new AuthForbidden('scope');
|
|
44
|
-
}
|
|
45
|
-
// The SAME resolver the guard uses, so a minted token is one this deployment's guard claims.
|
|
46
|
-
const minted = mintAccessToken(prefixOf(ctx));
|
|
47
|
-
const now = new Date();
|
|
48
|
-
const record = await tokens(ctx).create({
|
|
49
|
-
hash: minted.hash,
|
|
50
|
-
display: minted.display,
|
|
51
|
-
name: payload.name,
|
|
52
|
-
userId: subject.userId,
|
|
53
|
-
profileId: subject.profileId,
|
|
54
|
-
entityId: subject.entityId,
|
|
55
|
-
scopes,
|
|
56
|
-
role: subject.role,
|
|
57
|
-
createdAt: now,
|
|
58
|
-
...(payload.audience != null ? { audience: payload.audience } : {}),
|
|
59
|
-
...(payload.expiresIn != null
|
|
60
|
-
? { expiresAt: new Date(now.getTime() + Math.min(payload.expiresIn * 1000, AUTH_TOKEN_MAX_TTL)) }
|
|
61
|
-
: {}),
|
|
62
|
-
});
|
|
63
|
-
// The only moment the plaintext exists outside the caller's own machine.
|
|
64
|
-
return { token: minted.token, record: view(record) };
|
|
65
|
-
};
|
|
66
|
-
export const listAccessTokens = (protocol) => handlers().request(protocol, async (req, context) => {
|
|
67
|
-
const ctx = context;
|
|
68
|
-
const entityId = requireEntityKey(req);
|
|
69
|
-
const profileId = req.auth?.profileId;
|
|
70
|
-
if (profileId == null)
|
|
71
|
-
throw new AuthForbidden('profile');
|
|
72
|
-
const result = await tokens(ctx).list({ entityId, profileId }, { size: 0 });
|
|
73
|
-
return {
|
|
74
|
-
items: result.items
|
|
75
|
-
.map(view)
|
|
76
|
-
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()),
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
export const createAccessToken = (protocol) => handlers().body(protocol, async (payload, context, req) => {
|
|
80
|
-
const ctx = context;
|
|
81
|
-
refuseTokenAuth(req, 'token-mint');
|
|
82
|
-
const entityId = requireEntityKey(req);
|
|
83
|
-
const profileId = req.auth?.profileId;
|
|
84
|
-
const userId = req.auth?.userId;
|
|
85
|
-
if (profileId == null || userId == null)
|
|
86
|
-
throw new AuthForbidden('profile');
|
|
87
|
-
return issueAccessToken(ctx, {
|
|
88
|
-
entityId, profileId, userId, role: req.auth?.role ?? AuthRole.User, scopes: req.auth?.scopes ?? [],
|
|
89
|
-
}, payload);
|
|
90
|
-
});
|
|
91
|
-
export const revokeAccessToken = (protocol) => handlers().params(protocol, async (payload, context, req) => {
|
|
92
|
-
const ctx = context;
|
|
93
|
-
refuseTokenAuth(req, 'token-revoke');
|
|
94
|
-
const entityId = requireEntityKey(req);
|
|
95
|
-
const profileId = req.auth?.profileId;
|
|
96
|
-
if (profileId == null)
|
|
97
|
-
throw new AuthForbidden('profile');
|
|
98
|
-
const record = await tokens(ctx).load(payload.id);
|
|
99
|
-
// A token of another profile answers exactly as an unknown id does — an owner learns nothing
|
|
100
|
-
// about tokens that are not theirs, not even that one exists.
|
|
101
|
-
if (record == null || record.entityId !== entityId || record.profileId !== profileId) {
|
|
102
|
-
throw new AuthForbidden('token');
|
|
103
|
-
}
|
|
104
|
-
// Idempotent: revoking twice is what a retry looks like.
|
|
105
|
-
if (record.revokedAt == null) {
|
|
106
|
-
await tokens(ctx).save({ ...record, revokedAt: new Date(), updatedAt: new Date() });
|
|
107
|
-
}
|
|
108
|
-
return { id: payload.id };
|
|
109
|
-
});
|
|
1
|
+
import { accessTokenIssuerOf } from './issue.js';
|
|
2
|
+
export * from './refuse.js';
|
|
3
|
+
export * from './issue.js';
|
|
4
|
+
export * from './list.js';
|
|
5
|
+
export * from './create.js';
|
|
6
|
+
export * from './revoke.js';
|
|
7
|
+
/** @deprecated compat:factory-refactor — use `accessTokenIssuerOf(ctx).issueAccessToken(…)` */
|
|
8
|
+
export const issueAccessToken = async (ctx, subject, payload) => await accessTokenIssuerOf(ctx).issueAccessToken(subject, payload);
|
|
110
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAGhD,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAI3B,+FAA+F;AAC/F,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EACnC,GAAqB,EAAE,OAAgC,EAAE,OAAgC,EAC7D,EAAE,CAAC,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AuthTokenContext } from '../types.js';
|
|
2
|
+
import type { AccessTokenIssuer } from './types.js';
|
|
3
|
+
export declare const makeAccessTokenIssuer: (ctx: AuthTokenContext) => AccessTokenIssuer;
|
|
4
|
+
/** The token issuer of a context — one per context. */
|
|
5
|
+
export declare const accessTokenIssuerOf: (target: AuthTokenContext) => AccessTokenIssuer;
|
|
6
|
+
//# sourceMappingURL=issue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../../src/handlers/issue.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAoD,MAAM,YAAY,CAAA;AAGrG,eAAO,MAAM,qBAAqB,QAAS,gBAAgB,KAAG,iBAqC7D,CAAA;AAED,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,iDAA4C,CAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AuthForbidden, ALL_SCOPES } from '@owlmeans/auth';
|
|
2
|
+
import { AUTH_TOKEN_MAX_TTL } from '@owlmeans/auth-token';
|
|
3
|
+
import { memoHelper } from '@owlmeans/context';
|
|
4
|
+
import { tokenHashHelper } from '../hash.js';
|
|
5
|
+
import { prefixOf } from '../prefix.js';
|
|
6
|
+
import { tokenRecordUtils } from './utils.js';
|
|
7
|
+
export const makeAccessTokenIssuer = (ctx) => {
|
|
8
|
+
const issueAccessToken = async (subject, payload) => {
|
|
9
|
+
const own = subject.scopes;
|
|
10
|
+
const scopes = payload.scopes ?? own;
|
|
11
|
+
// A token is a delegation, so it can only ever narrow. Asking for a scope the caller does not
|
|
12
|
+
// hold is refused rather than silently dropped: a token that quietly grants less than it was
|
|
13
|
+
// asked for fails later, somewhere else, with an error about the wrong thing.
|
|
14
|
+
if (!own.includes(ALL_SCOPES) && scopes.some(scope => !own.includes(scope))) {
|
|
15
|
+
throw new AuthForbidden('scope');
|
|
16
|
+
}
|
|
17
|
+
// The SAME resolver the guard uses, so a minted token is one this deployment's guard claims.
|
|
18
|
+
const minted = tokenHashHelper.mintAccessToken(prefixOf(ctx));
|
|
19
|
+
const now = new Date();
|
|
20
|
+
const record = await tokenRecordUtils.tokens(ctx).create({
|
|
21
|
+
hash: minted.hash,
|
|
22
|
+
display: minted.display,
|
|
23
|
+
name: payload.name,
|
|
24
|
+
userId: subject.userId,
|
|
25
|
+
profileId: subject.profileId,
|
|
26
|
+
entityId: subject.entityId,
|
|
27
|
+
scopes,
|
|
28
|
+
role: subject.role,
|
|
29
|
+
createdAt: now,
|
|
30
|
+
...(payload.audience != null ? { audience: payload.audience } : {}),
|
|
31
|
+
...(payload.expiresIn != null
|
|
32
|
+
? { expiresAt: new Date(now.getTime() + Math.min(payload.expiresIn * 1000, AUTH_TOKEN_MAX_TTL)) }
|
|
33
|
+
: {}),
|
|
34
|
+
});
|
|
35
|
+
// The only moment the plaintext exists outside the caller's own machine.
|
|
36
|
+
return { token: minted.token, record: tokenRecordUtils.view(record) };
|
|
37
|
+
};
|
|
38
|
+
return { issueAccessToken };
|
|
39
|
+
};
|
|
40
|
+
/** The token issuer of a context — one per context. */
|
|
41
|
+
export const accessTokenIssuerOf = memoHelper.oncePer(makeAccessTokenIssuer);
|
|
42
|
+
//# sourceMappingURL=issue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../../src/handlers/issue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,kBAAkB,EAAkD,MAAM,sBAAsB,CAAA;AACzG,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAGvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAqB,EAAqB,EAAE;IAChF,MAAM,gBAAgB,GAAG,KAAK,EAC5B,OAAgC,EAAE,OAAgC,EACtC,EAAE;QAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAA;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,GAAG,CAAA;QACpC,8FAA8F;QAC9F,6FAA6F;QAC7F,8EAA8E;QAC9E,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YAC5E,MAAM,IAAI,aAAa,CAAC,OAAO,CAAC,CAAA;QAClC,CAAC;QAED,6FAA6F;QAC7F,MAAM,MAAM,GAAG,eAAe,CAAC,eAAe,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;QAC7D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;QACtB,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;YACvD,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,MAAM;YACN,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,SAAS,EAAE,GAAG;YACd,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,IAAI;gBAC3B,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE;gBACjG,CAAC,CAAC,EAAE,CAAC;SACa,CAAC,CAAA;QAEvB,yEAAyE;QACzE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,EAA8B,CAAA;IACnG,CAAC,CAAA;IAED,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC7B,CAAC,CAAA;AAED,uDAAuD;AACvD,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAA"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AccessTokenList, AuthTokenEntrypoints } from '@owlmeans/auth-token';
|
|
2
|
+
export declare const listAccessTokens: (protocol: AuthTokenEntrypoints['list']) => import("@owlmeans/server-entrypoint").BoundEntrypointHandler<import("@owlmeans/entrypoint").EntrypointProtocol<import("@owlmeans/entrypoint").OpenRequest, AccessTokenList>>;
|
|
3
|
+
//# sourceMappingURL=list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/handlers/list.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAMjF,eAAO,MAAM,gBAAgB,aAAc,oBAAoB,CAAC,MAAM,CAAC,iLAarE,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AuthForbidden } from '@owlmeans/auth';
|
|
2
|
+
import { makeEntityScope } from '@owlmeans/auth-common';
|
|
3
|
+
import { handlers } from '@owlmeans/server-api';
|
|
4
|
+
import { tokenRecordUtils } from './utils.js';
|
|
5
|
+
export const listAccessTokens = (protocol) => handlers().request(protocol, async (req, context) => {
|
|
6
|
+
const ctx = context;
|
|
7
|
+
const entityId = makeEntityScope(req).requireEntityKey();
|
|
8
|
+
const profileId = req.auth?.profileId;
|
|
9
|
+
if (profileId == null)
|
|
10
|
+
throw new AuthForbidden('profile');
|
|
11
|
+
const result = await tokenRecordUtils.tokens(ctx).list({ entityId, profileId }, { size: 0 });
|
|
12
|
+
return {
|
|
13
|
+
items: result.items
|
|
14
|
+
.map(tokenRecordUtils.view)
|
|
15
|
+
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/handlers/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,QAAsC,EAAE,EAAE,CAAC,QAAQ,EAAoB,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;IAChJ,MAAM,GAAG,GAAG,OAA2B,CAAA;IACvC,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,gBAAgB,EAAE,CAAA;IACxD,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,CAAA;IACrC,IAAI,SAAS,IAAI,IAAI;QAAE,MAAM,IAAI,aAAa,CAAC,SAAS,CAAC,CAAA;IAEzD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;IAE5F,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK;aAChB,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC;aAC1B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;KAC3D,CAAA;AAC7B,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A token may never mint another token.
|
|
3
|
+
*
|
|
4
|
+
* Minting is the one operation that turns a stolen credential into a permanent one: a token that
|
|
5
|
+
* can create tokens survives the revocation of the token that leaked. Creating and revoking
|
|
6
|
+
* therefore require a credential a person produced in a browser — which is also why the deployment
|
|
7
|
+
* names these routes in the guard's deny list, so the refusal is a 401 at the boundary rather than
|
|
8
|
+
* a check every future handler has to remember.
|
|
9
|
+
*
|
|
10
|
+
* Exported because an OAuth authorization server's consent-approval handler
|
|
11
|
+
* (`@owlmeans/server-oauth`) mints tokens too and needs the exact same guard, over a session it
|
|
12
|
+
* verified itself rather than a route this package bound.
|
|
13
|
+
*/
|
|
14
|
+
export declare const refuseTokenAuth: (req: {
|
|
15
|
+
auth?: {
|
|
16
|
+
type?: string;
|
|
17
|
+
};
|
|
18
|
+
}, what: string) => void;
|
|
19
|
+
//# sourceMappingURL=refuse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refuse.d.ts","sourceRoot":"","sources":["../../src/handlers/refuse.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,QAAS;IAAE,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,QAAQ,MAAM,KAAG,IAIjF,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AuthForbidden, AuthroizationType } from '@owlmeans/auth';
|
|
2
|
+
/**
|
|
3
|
+
* A token may never mint another token.
|
|
4
|
+
*
|
|
5
|
+
* Minting is the one operation that turns a stolen credential into a permanent one: a token that
|
|
6
|
+
* can create tokens survives the revocation of the token that leaked. Creating and revoking
|
|
7
|
+
* therefore require a credential a person produced in a browser — which is also why the deployment
|
|
8
|
+
* names these routes in the guard's deny list, so the refusal is a 401 at the boundary rather than
|
|
9
|
+
* a check every future handler has to remember.
|
|
10
|
+
*
|
|
11
|
+
* Exported because an OAuth authorization server's consent-approval handler
|
|
12
|
+
* (`@owlmeans/server-oauth`) mints tokens too and needs the exact same guard, over a session it
|
|
13
|
+
* verified itself rather than a route this package bound.
|
|
14
|
+
*/
|
|
15
|
+
export const refuseTokenAuth = (req, what) => {
|
|
16
|
+
if (req.auth?.type === AuthroizationType.AuthToken) {
|
|
17
|
+
throw new AuthForbidden(what);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=refuse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refuse.js","sourceRoot":"","sources":["../../src/handlers/refuse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEjE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAiC,EAAE,IAAY,EAAQ,EAAE;IACvF,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,iBAAiB,CAAC,SAAS,EAAE,CAAC;QACnD,MAAM,IAAI,aAAa,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuthTokenEntrypoints } from '@owlmeans/auth-token';
|
|
2
|
+
export declare const revokeAccessToken: (protocol: AuthTokenEntrypoints['revoke']) => import("@owlmeans/server-entrypoint").BoundEntrypointHandler<import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
3
|
+
params: import("@owlmeans/auth-token").AccessTokenParams;
|
|
4
|
+
}, {
|
|
5
|
+
id: string;
|
|
6
|
+
}>>;
|
|
7
|
+
//# sourceMappingURL=revoke.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../src/handlers/revoke.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAOhE,eAAO,MAAM,iBAAiB,aAAc,oBAAoB,CAAC,QAAQ,CAAC;;;;GAqBxE,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AuthForbidden } from '@owlmeans/auth';
|
|
2
|
+
import { makeEntityScope } from '@owlmeans/auth-common';
|
|
3
|
+
import { handlers } from '@owlmeans/server-api';
|
|
4
|
+
import { refuseTokenAuth } from './refuse.js';
|
|
5
|
+
import { tokenRecordUtils } from './utils.js';
|
|
6
|
+
export const revokeAccessToken = (protocol) => handlers().params(protocol, async (payload, context, req) => {
|
|
7
|
+
const ctx = context;
|
|
8
|
+
refuseTokenAuth(req, 'token-revoke');
|
|
9
|
+
const entityId = makeEntityScope(req).requireEntityKey();
|
|
10
|
+
const profileId = req.auth?.profileId;
|
|
11
|
+
if (profileId == null)
|
|
12
|
+
throw new AuthForbidden('profile');
|
|
13
|
+
const record = await tokenRecordUtils.tokens(ctx).load(payload.id);
|
|
14
|
+
// A token of another profile answers exactly as an unknown id does — an owner learns nothing
|
|
15
|
+
// about tokens that are not theirs, not even that one exists.
|
|
16
|
+
if (record == null || record.entityId !== entityId || record.profileId !== profileId) {
|
|
17
|
+
throw new AuthForbidden('token');
|
|
18
|
+
}
|
|
19
|
+
// Idempotent: revoking twice is what a retry looks like.
|
|
20
|
+
if (record.revokedAt == null) {
|
|
21
|
+
await tokenRecordUtils.tokens(ctx).save({ ...record, revokedAt: new Date(), updatedAt: new Date() });
|
|
22
|
+
}
|
|
23
|
+
return { id: payload.id };
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=revoke.js.map
|