@c9up/visa 0.1.0 → 0.1.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,UAAU,QAAQ;IACjB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,SAAS,CACR,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,aAAa,CACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EACjD,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/C;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjE"}
1
+ {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAgBH,UAAU,QAAQ;IACjB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,SAAS,CACR,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,aAAa,CACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EACjD,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC3B,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/C;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBjE"}
package/dist/configure.js CHANGED
@@ -10,8 +10,23 @@
10
10
  * in a way that only shows up in production: a memory store loses every
11
11
  * session on deploy, and a consent screen nobody wrote grants silently.
12
12
  */
13
+ import { access, constants } from "node:fs/promises";
14
+ import { resolve } from "node:path";
13
15
  import { stubsRoot } from "./stubs.js";
16
+ /**
17
+ * The migration this package ships.
18
+ *
19
+ * A high, stable prefix rather than a timestamp: these five tables reference
20
+ * nothing else, so running them last is fine, and a number nobody's own
21
+ * migrations are likely to have taken means the file lands instead of silently
22
+ * losing to a name that already exists.
23
+ */
24
+ const MIGRATION_STUB = "database/migrations/9000_create_visa_tables.stub";
14
25
  export async function configure(codemods) {
26
+ // Fail fast BEFORE touching the project: a pruned tarball or an unreadable
27
+ // stub must not leave an application with a provider registered, a config
28
+ // written and no tables to put anything in.
29
+ await access(resolve(stubsRoot, MIGRATION_STUB), constants.R_OK);
15
30
  // The config below reads this, so it is declared here. Writing the file
16
31
  // without it leaves an application whose config asks the environment for
17
32
  // something nothing ever put there.
@@ -20,5 +35,9 @@ export async function configure(codemods) {
20
35
  });
21
36
  await codemods.addProvider("@c9up/visa/provider");
22
37
  await codemods.makeUsingStub(stubsRoot, "config/visa.stub");
38
+ // The tables the atlas store reads and writes. An application on another
39
+ // data layer deletes the file and keeps its own store — which is why this
40
+ // is a migration and not a schema the package owns.
41
+ await codemods.makeUsingStub(stubsRoot, MIGRATION_STUB);
23
42
  }
24
43
  //# sourceMappingURL=configure.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"configure.js","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAkBvC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAkB;IACjD,wEAAwE;IACxE,yEAAyE;IACzE,oCAAoC;IACpC,MAAM,QAAQ,CAAC,UAAU,CAAC;QACzB,WAAW,EAAE,uBAAuB;KACpC,CAAC,CAAC;IAEH,MAAM,QAAQ,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAClD,MAAM,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;AAC7D,CAAC"}
1
+ {"version":3,"file":"configure.js","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,kDAAkD,CAAC;AAkB1E,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAkB;IACjD,2EAA2E;IAC3E,0EAA0E;IAC1E,4CAA4C;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IAEjE,wEAAwE;IACxE,yEAAyE;IACzE,oCAAoC;IACpC,MAAM,QAAQ,CAAC,UAAU,CAAC;QACzB,WAAW,EAAE,uBAAuB;KACpC,CAAC,CAAC;IAEH,MAAM,QAAQ,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAClD,MAAM,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IAC5D,yEAAyE;IACzE,0EAA0E;IAC1E,oDAAoD;IACpD,MAAM,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,222 @@
1
+ /**
2
+ * `visaGuard()` — authenticating a request with an OAuth access token.
3
+ *
4
+ * Declared in `config/auth.ts` beside `jwtGuard` and `sessionGuard`, and it
5
+ * behaves like the access-tokens guard upstream: the authenticated user carries
6
+ * a `currentAccessToken` whose `abilities` are the token's scopes, with
7
+ * `allows`, `denies`, `authorize`, `isExpired()` and `lastUsedAt` answering
8
+ * exactly as they do there. `allows` treats `*` as every ability, which is the
9
+ * upstream rule read off `@adonisjs/auth`'s build rather than recalled.
10
+ *
11
+ * Two things this fixes for every application at once. A route gate no longer
12
+ * has to introspect the token itself — the scopes are on the guard's token. And
13
+ * the user is fetched through the application's own provider on every request,
14
+ * so an account disabled after the token was issued is refused here instead of
15
+ * in each application that remembers to check.
16
+ *
17
+ * NO import of warden. A guard is structurally `{ name, authenticate, verify }`
18
+ * and that is all warden asks for, so visa stays a package you can run without
19
+ * it — the same rule every other package in this universe follows.
20
+ */
21
+ import type { VisaStore } from "./store.js";
22
+ /**
23
+ * The user shape a guard hands back.
24
+ *
25
+ * `id` and the two optional arrays are what warden reads; everything else the
26
+ * application's provider returns travels with it untouched.
27
+ */
28
+ export interface GuardUser {
29
+ id: string;
30
+ roles?: string[];
31
+ permissions?: string[];
32
+ /** The token this request was authenticated with. Set by the guard. */
33
+ currentAccessToken?: VisaAccessToken;
34
+ [key: string]: unknown;
35
+ }
36
+ /**
37
+ * What the guard needs from the store, and nothing more.
38
+ *
39
+ * A narrow slice rather than the whole contract: authenticating reads one row
40
+ * and records one use, so that is what it asks for. A full {@link VisaStore}
41
+ * satisfies it, and a resource server that keeps only tokens can implement two
42
+ * methods instead of twelve.
43
+ */
44
+ export type GuardStore = Pick<VisaStore, "findAccessToken" | "touchAccessToken" | "saveAccessToken" | "revokeAccessToken">;
45
+ /** What a guard answers. Warden's `AuthResult`, declared here so visa owes it nothing. */
46
+ export interface GuardResult {
47
+ authenticated: boolean;
48
+ user?: GuardUser;
49
+ error?: string;
50
+ }
51
+ /** What a test client sends to be this user. Warden's `AuthClientResponse`. */
52
+ export interface GuardClientResponse {
53
+ headers?: Record<string, string>;
54
+ session?: Record<string, unknown>;
55
+ }
56
+ /**
57
+ * The token behind the current request.
58
+ *
59
+ * Upstream's `AccessToken`, minus what an opaque OAuth token has no equivalent
60
+ * for: there is no `identifier`/`secret` pair to decode here, because visa
61
+ * stores a hash and the plaintext never comes back.
62
+ */
63
+ export declare class VisaAccessToken {
64
+ /** The client the token was issued to — an OAuth token always has one. */
65
+ readonly clientId: string;
66
+ /** Absent for `client_credentials`: there is no user behind it. */
67
+ readonly userId?: string;
68
+ /** The token's scopes. Named `abilities` for the upstream surface. */
69
+ readonly abilities: string[];
70
+ /**
71
+ * A recognisable name, as upstream carries one.
72
+ *
73
+ * An OAuth token is not named by the user who holds it — it is named by the
74
+ * client it was issued to — so this is the client's name when the store
75
+ * knows it, and null otherwise.
76
+ */
77
+ readonly name: string | null;
78
+ readonly expiresAt: Date | null;
79
+ readonly lastUsedAt: Date | null;
80
+ /**
81
+ * The token itself, and only just after it was created.
82
+ *
83
+ * Upstream's `value`: the one moment the plaintext exists is when it is
84
+ * minted, because what is stored is a hash. A token read back from the
85
+ * store never has it, and that is not a gap — it is why a database copy is
86
+ * not a set of working tokens.
87
+ */
88
+ readonly value?: string;
89
+ constructor(attributes: {
90
+ clientId: string;
91
+ userId?: string;
92
+ abilities: string[];
93
+ name?: string | null;
94
+ expiresAt?: Date | null;
95
+ lastUsedAt?: Date | null;
96
+ value?: string;
97
+ });
98
+ /** `*` means every ability — upstream's rule, read off its build. */
99
+ allows(ability: string): boolean;
100
+ denies(ability: string): boolean;
101
+ /**
102
+ * Throw unless the token carries `ability`.
103
+ *
104
+ * The upstream error id verbatim, so a handler that already maps
105
+ * `E_UNAUTHORIZED_ACCESS` keeps working.
106
+ */
107
+ authorize(ability: string): void;
108
+ /** A token with no expiry never expires. */
109
+ isExpired(): boolean;
110
+ /** Upstream's keys, plus the client an OAuth token always belongs to. */
111
+ toJSON(): {
112
+ type: "bearer";
113
+ name: string | null;
114
+ token: string | undefined;
115
+ clientId: string;
116
+ abilities: string[];
117
+ lastUsedAt: Date | null;
118
+ expiresAt: Date | null;
119
+ };
120
+ }
121
+ export interface VisaGuardConfig {
122
+ /**
123
+ * Where the tokens live. A function, so the guard can be built in
124
+ * `config/auth.ts` before the provider has seated the manager — config is
125
+ * read at boot, and the store exists a moment later.
126
+ */
127
+ store: GuardStore | (() => GuardStore);
128
+ /**
129
+ * The application's user provider — upstream's `tokensUserProvider({ model })`.
130
+ *
131
+ * Called on every authenticated request, deliberately: a token issued to an
132
+ * account that has since been disabled must stop working, and the only way
133
+ * to know is to ask. Answer `null` and the request is refused.
134
+ */
135
+ findUser: (userId: string) => Promise<GuardUser | null>;
136
+ /**
137
+ * What a `client_credentials` token authenticates as, when anything.
138
+ *
139
+ * There is no user behind that grant, so by default such a token does not
140
+ * authenticate a user at all and the guard refuses. An application that
141
+ * serves machine callers supplies this and gets a subject of its choosing.
142
+ */
143
+ findClientSubject?: (clientId: string) => Promise<GuardUser | null>;
144
+ /** Guard name, for `auth.use(name)`. Defaults to `visa`. */
145
+ name?: string;
146
+ /**
147
+ * The client a token minted by {@link VisaGuard.createToken} belongs to.
148
+ *
149
+ * Upstream's guard needs no such thing because it has no clients. Here
150
+ * every token is issued TO somebody, and one with no client could not be
151
+ * introspected, revoked by its owner, or listed on a "connected
152
+ * applications" screen. Name the first-party application and a minted
153
+ * token is an ordinary token in every other respect.
154
+ */
155
+ tokenClientId?: string;
156
+ /** How long a minted token lasts — upstream's `expiresIn`, in seconds. */
157
+ expiresInSeconds?: number;
158
+ }
159
+ /**
160
+ * A guard, in the shape warden's config expects.
161
+ *
162
+ * Shared per application, so it holds no per-request state — the same rule
163
+ * warden states for its own strategies.
164
+ */
165
+ export declare class VisaGuard {
166
+ #private;
167
+ readonly name: string;
168
+ constructor(config: VisaGuardConfig);
169
+ /**
170
+ * There is no password flow here, and OAuth 2.1 removed the one there used
171
+ * to be. A token comes from the token endpoint.
172
+ *
173
+ * THROWS rather than answering "not authenticated", which is what warden's
174
+ * own access-tokens guard does: a bearer guard handed an email and a
175
+ * password is a misconfigured application, not a bad credential, and the two
176
+ * must not produce the same 401.
177
+ */
178
+ authenticate(): Promise<GuardResult>;
179
+ /**
180
+ * Mint a token for `user`, as upstream's guard does.
181
+ *
182
+ * The token is a real one: stored hashed, listed, introspectable,
183
+ * revocable. What it skips is the authorization code flow, so it is for a
184
+ * FIRST-PARTY client you own — `tokenClientId` — and nothing here should
185
+ * ever be handed a third-party client id.
186
+ */
187
+ createToken(user: {
188
+ id: string;
189
+ } | string, abilities?: string[], options?: {
190
+ name?: string;
191
+ expiresInSeconds?: number;
192
+ }): Promise<VisaAccessToken>;
193
+ /**
194
+ * Revoke a token — upstream's `invalidateToken`.
195
+ *
196
+ * NAMED DEVIATION — upstream's guard is built per request and invalidates
197
+ * the token it just authenticated with, so it takes no argument. A warden
198
+ * strategy is shared by the whole application and holds no per-request
199
+ * state, by warden's own rule, so the token to revoke is passed in. A
200
+ * sign-out handler has it: it is the bearer credential of the request.
201
+ *
202
+ * Answers whether the token was there to revoke.
203
+ */
204
+ invalidateToken(presented: string): Promise<boolean>;
205
+ /**
206
+ * What a test client sends to be `user` — upstream's signature.
207
+ *
208
+ * It mints a token, as upstream does, so a test authenticates the way the
209
+ * application will rather than against a credential the store has never
210
+ * seen.
211
+ */
212
+ authenticateAsClient(user: {
213
+ id: string;
214
+ } | string, abilities?: string[], options?: {
215
+ name?: string;
216
+ expiresInSeconds?: number;
217
+ }): Promise<GuardClientResponse>;
218
+ verify(presented: string): Promise<GuardResult>;
219
+ }
220
+ /** Build a visa guard from its config — `visaGuard({...})` in `config/auth.ts`. */
221
+ export declare function visaGuard(config: VisaGuardConfig): VisaGuard;
222
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
package/dist/guard.js ADDED
@@ -0,0 +1,274 @@
1
+ /**
2
+ * `visaGuard()` — authenticating a request with an OAuth access token.
3
+ *
4
+ * Declared in `config/auth.ts` beside `jwtGuard` and `sessionGuard`, and it
5
+ * behaves like the access-tokens guard upstream: the authenticated user carries
6
+ * a `currentAccessToken` whose `abilities` are the token's scopes, with
7
+ * `allows`, `denies`, `authorize`, `isExpired()` and `lastUsedAt` answering
8
+ * exactly as they do there. `allows` treats `*` as every ability, which is the
9
+ * upstream rule read off `@adonisjs/auth`'s build rather than recalled.
10
+ *
11
+ * Two things this fixes for every application at once. A route gate no longer
12
+ * has to introspect the token itself — the scopes are on the guard's token. And
13
+ * the user is fetched through the application's own provider on every request,
14
+ * so an account disabled after the token was issued is refused here instead of
15
+ * in each application that remembers to check.
16
+ *
17
+ * NO import of warden. A guard is structurally `{ name, authenticate, verify }`
18
+ * and that is all warden asks for, so visa stays a package you can run without
19
+ * it — the same rule every other package in this universe follows.
20
+ */
21
+ import { hashSecret, randomToken } from "./crypto.js";
22
+ import { verifyAccessToken } from "./introspect.js";
23
+ /**
24
+ * The token behind the current request.
25
+ *
26
+ * Upstream's `AccessToken`, minus what an opaque OAuth token has no equivalent
27
+ * for: there is no `identifier`/`secret` pair to decode here, because visa
28
+ * stores a hash and the plaintext never comes back.
29
+ */
30
+ export class VisaAccessToken {
31
+ /** The client the token was issued to — an OAuth token always has one. */
32
+ clientId;
33
+ /** Absent for `client_credentials`: there is no user behind it. */
34
+ userId;
35
+ /** The token's scopes. Named `abilities` for the upstream surface. */
36
+ abilities;
37
+ /**
38
+ * A recognisable name, as upstream carries one.
39
+ *
40
+ * An OAuth token is not named by the user who holds it — it is named by the
41
+ * client it was issued to — so this is the client's name when the store
42
+ * knows it, and null otherwise.
43
+ */
44
+ name;
45
+ expiresAt;
46
+ lastUsedAt;
47
+ /**
48
+ * The token itself, and only just after it was created.
49
+ *
50
+ * Upstream's `value`: the one moment the plaintext exists is when it is
51
+ * minted, because what is stored is a hash. A token read back from the
52
+ * store never has it, and that is not a gap — it is why a database copy is
53
+ * not a set of working tokens.
54
+ */
55
+ value;
56
+ constructor(attributes) {
57
+ this.clientId = attributes.clientId;
58
+ if (attributes.userId !== undefined)
59
+ this.userId = attributes.userId;
60
+ this.abilities = [...attributes.abilities];
61
+ this.name = attributes.name ?? null;
62
+ this.expiresAt = attributes.expiresAt ?? null;
63
+ this.lastUsedAt = attributes.lastUsedAt ?? null;
64
+ if (attributes.value !== undefined)
65
+ this.value = attributes.value;
66
+ }
67
+ /** `*` means every ability — upstream's rule, read off its build. */
68
+ allows(ability) {
69
+ return this.abilities.includes(ability) || this.abilities.includes("*");
70
+ }
71
+ denies(ability) {
72
+ return !this.allows(ability);
73
+ }
74
+ /**
75
+ * Throw unless the token carries `ability`.
76
+ *
77
+ * The upstream error id verbatim, so a handler that already maps
78
+ * `E_UNAUTHORIZED_ACCESS` keeps working.
79
+ */
80
+ authorize(ability) {
81
+ if (this.allows(ability))
82
+ return;
83
+ const error = new Error(`Access denied: the token does not carry "${ability}"`);
84
+ error.code = "E_UNAUTHORIZED_ACCESS";
85
+ error.status = 403;
86
+ throw error;
87
+ }
88
+ /** A token with no expiry never expires. */
89
+ isExpired() {
90
+ if (this.expiresAt === null)
91
+ return false;
92
+ return this.expiresAt.getTime() <= Date.now();
93
+ }
94
+ /** Upstream's keys, plus the client an OAuth token always belongs to. */
95
+ toJSON() {
96
+ return {
97
+ type: "bearer",
98
+ name: this.name,
99
+ // Present exactly once, on the token `createToken` just returned —
100
+ // which is the only time a caller can hand it to anybody.
101
+ token: this.value,
102
+ clientId: this.clientId,
103
+ abilities: this.abilities,
104
+ lastUsedAt: this.lastUsedAt,
105
+ expiresAt: this.expiresAt,
106
+ };
107
+ }
108
+ }
109
+ /** An hour, as the token endpoint uses by default. */
110
+ const DEFAULT_TTL = 3600;
111
+ /**
112
+ * A guard, in the shape warden's config expects.
113
+ *
114
+ * Shared per application, so it holds no per-request state — the same rule
115
+ * warden states for its own strategies.
116
+ */
117
+ export class VisaGuard {
118
+ name;
119
+ #config;
120
+ constructor(config) {
121
+ this.#config = config;
122
+ this.name = config.name ?? "visa";
123
+ }
124
+ #store() {
125
+ const store = this.#config.store;
126
+ return typeof store === "function" ? store() : store;
127
+ }
128
+ /**
129
+ * There is no password flow here, and OAuth 2.1 removed the one there used
130
+ * to be. A token comes from the token endpoint.
131
+ *
132
+ * THROWS rather than answering "not authenticated", which is what warden's
133
+ * own access-tokens guard does: a bearer guard handed an email and a
134
+ * password is a misconfigured application, not a bad credential, and the two
135
+ * must not produce the same 401.
136
+ */
137
+ async authenticate() {
138
+ throw new Error("visa authenticates a bearer token, not credentials. Use verify() with the access token.");
139
+ }
140
+ /**
141
+ * Mint a token for `user`, as upstream's guard does.
142
+ *
143
+ * The token is a real one: stored hashed, listed, introspectable,
144
+ * revocable. What it skips is the authorization code flow, so it is for a
145
+ * FIRST-PARTY client you own — `tokenClientId` — and nothing here should
146
+ * ever be handed a third-party client id.
147
+ */
148
+ async createToken(user, abilities = ["*"], options = {}) {
149
+ const clientId = this.#config.tokenClientId;
150
+ if (clientId === undefined) {
151
+ throw new Error("[visa] createToken needs `tokenClientId` — a token is always issued to a client. Name the first-party application in visaGuard({ tokenClientId }).");
152
+ }
153
+ const userId = typeof user === "string" ? user : user.id;
154
+ const ttl = options.expiresInSeconds ?? this.#config.expiresInSeconds ?? DEFAULT_TTL;
155
+ const secret = randomToken();
156
+ const expiresAt = new Date(Date.now() + ttl * 1000);
157
+ await this.#store().saveAccessToken({
158
+ tokenHash: hashSecret(secret),
159
+ clientId,
160
+ userId,
161
+ scopes: abilities,
162
+ expiresAt,
163
+ });
164
+ return new VisaAccessToken({
165
+ clientId,
166
+ userId,
167
+ abilities,
168
+ name: options.name ?? null,
169
+ expiresAt,
170
+ // The one moment the plaintext exists.
171
+ value: secret,
172
+ });
173
+ }
174
+ /**
175
+ * Revoke a token — upstream's `invalidateToken`.
176
+ *
177
+ * NAMED DEVIATION — upstream's guard is built per request and invalidates
178
+ * the token it just authenticated with, so it takes no argument. A warden
179
+ * strategy is shared by the whole application and holds no per-request
180
+ * state, by warden's own rule, so the token to revoke is passed in. A
181
+ * sign-out handler has it: it is the bearer credential of the request.
182
+ *
183
+ * Answers whether the token was there to revoke.
184
+ */
185
+ async invalidateToken(presented) {
186
+ const store = this.#store();
187
+ const hash = hashSecret(presented);
188
+ const existing = await store.findAccessToken(hash);
189
+ if (existing === null || existing.revokedAt !== undefined)
190
+ return false;
191
+ await store.revokeAccessToken(hash, new Date());
192
+ return true;
193
+ }
194
+ /**
195
+ * What a test client sends to be `user` — upstream's signature.
196
+ *
197
+ * It mints a token, as upstream does, so a test authenticates the way the
198
+ * application will rather than against a credential the store has never
199
+ * seen.
200
+ */
201
+ async authenticateAsClient(user, abilities = ["*"], options = {}) {
202
+ const token = await this.createToken(user, abilities, options);
203
+ return { headers: { authorization: `Bearer ${token.value}` } };
204
+ }
205
+ async verify(presented) {
206
+ const store = this.#store();
207
+ const now = new Date();
208
+ const verified = await verifyAccessToken(presented, store, now);
209
+ if (verified === null) {
210
+ return { authenticated: false, error: "Invalid or expired access token" };
211
+ }
212
+ const subject = verified.userId === undefined
213
+ ? await this.#config.findClientSubject?.(verified.clientId)
214
+ : await this.#config.findUser(verified.userId);
215
+ if (subject === null || subject === undefined) {
216
+ // The account is gone or disabled since the token was minted. Refusing
217
+ // here is the point of asking on every request.
218
+ return {
219
+ authenticated: false,
220
+ error: verified.userId === undefined
221
+ ? "This token authenticates no user"
222
+ : "The account behind this token is no longer available",
223
+ };
224
+ }
225
+ // The PREVIOUS use is what the token reports, and this one is recorded
226
+ // after — the same order upstream reads its row in.
227
+ await touch(store, presented, now);
228
+ const token = new VisaAccessToken({
229
+ clientId: verified.clientId,
230
+ ...(verified.userId === undefined ? {} : { userId: verified.userId }),
231
+ abilities: verified.scopes,
232
+ expiresAt: verified.expiresAt ?? null,
233
+ lastUsedAt: verified.lastUsedAt ?? null,
234
+ });
235
+ // A copy: the provider's object is the application's, and a guard that
236
+ // mutated it would leak one request's token into the next.
237
+ const user = { ...subject, currentAccessToken: token };
238
+ if (verified.scopes.length > 0) {
239
+ // Scopes surface on `permissions` as well, the way warden's access-token
240
+ // guard does it, so `ctx.auth.user.permissions` reads the same whichever
241
+ // guard authenticated.
242
+ //
243
+ // They remain a SEPARATE axis from the route-level rights gate, exactly
244
+ // as upstream: a token ability is checked with
245
+ // `currentAccessToken.allows(...)`, and Bouncer does not consult it.
246
+ user.permissions = [
247
+ ...new Set([...(subject.permissions ?? []), ...verified.scopes]),
248
+ ];
249
+ }
250
+ return { authenticated: true, user };
251
+ }
252
+ }
253
+ /**
254
+ * Record that the token was just used.
255
+ *
256
+ * Never fatal: a store with no `touchAccessToken` — the contract shipped
257
+ * without one — still authenticates, it simply reports no last use.
258
+ */
259
+ async function touch(store, presented, now) {
260
+ if (store.touchAccessToken === undefined)
261
+ return;
262
+ try {
263
+ await store.touchAccessToken(hashSecret(presented), now);
264
+ }
265
+ catch {
266
+ // Recording the use is bookkeeping; failing it must not refuse a request
267
+ // whose token is valid.
268
+ }
269
+ }
270
+ /** Build a visa guard from its config — `visaGuard({...})` in `config/auth.ts`. */
271
+ export function visaGuard(config) {
272
+ return new VisaGuard(config);
273
+ }
274
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -15,9 +15,11 @@ export { assertGrantAllowed, authenticateClient, type ClientCredentials, readCre
15
15
  export { defineConfig, type VisaConfigInput } from "./config.js";
16
16
  export { hashSecret, randomToken, secretMatches, sha256 } from "./crypto.js";
17
17
  export { type AuthorizationErrorCode, OAuthError, type ProtocolErrorCode, type TokenErrorCode, VisaError, } from "./errors.js";
18
+ export { type GuardClientResponse, type GuardResult, type GuardStore, type GuardUser, VisaAccessToken, VisaGuard, type VisaGuardConfig, visaGuard, } from "./guard.js";
18
19
  export { introspect, revoke, verifyAccessToken } from "./introspect.js";
19
20
  export { clearVisa, default as visa, getVisa, setVisa, } from "./services/main.js";
20
21
  export type { VisaStore } from "./store.js";
22
+ export { type AtlasDb, type AtlasQuery, AtlasStore, type AtlasStoreTables, } from "./stores/atlas.js";
21
23
  export { MemoryStore } from "./stores/memory.js";
22
24
  export { type TokenOptions, type TokenRequest, token } from "./token.js";
23
25
  export type { AccessToken, AuthorizationCode, Client, ClientAuthMethod, Consent, GrantType, IntrospectionResponse, RefreshToken, TokenResponse, } from "./types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,4BAA4B,GAC5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EACN,KAAK,sBAAsB,EAC3B,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,SAAS,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACN,SAAS,EACT,OAAO,IAAI,IAAI,EACf,OAAO,EACP,OAAO,GACP,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACzE,YAAY,EACX,WAAW,EACX,iBAAiB,EACjB,MAAM,EACN,gBAAgB,EAChB,OAAO,EACP,SAAS,EACT,qBAAqB,EACrB,YAAY,EACZ,aAAa,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,WAAW,GACX,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,4BAA4B,GAC5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EACN,KAAK,sBAAsB,EAC3B,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,SAAS,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,eAAe,EACf,SAAS,EACT,KAAK,eAAe,EACpB,SAAS,GACT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACN,SAAS,EACT,OAAO,IAAI,IAAI,EACf,OAAO,EACP,OAAO,GACP,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,UAAU,EACV,KAAK,gBAAgB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACzE,YAAY,EACX,WAAW,EACX,iBAAiB,EACjB,MAAM,EACN,gBAAgB,EAChB,OAAO,EACP,SAAS,EACT,qBAAqB,EACrB,YAAY,EACZ,aAAa,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,WAAW,GACX,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -15,8 +15,10 @@ export { assertGrantAllowed, authenticateClient, readCredentials, redirectUriMat
15
15
  export { defineConfig } from "./config.js";
16
16
  export { hashSecret, randomToken, secretMatches, sha256 } from "./crypto.js";
17
17
  export { OAuthError, VisaError, } from "./errors.js";
18
+ export { VisaAccessToken, VisaGuard, visaGuard, } from "./guard.js";
18
19
  export { introspect, revoke, verifyAccessToken } from "./introspect.js";
19
20
  export { clearVisa, default as visa, getVisa, setVisa, } from "./services/main.js";
21
+ export { AtlasStore, } from "./stores/atlas.js";
20
22
  export { MemoryStore } from "./stores/memory.js";
21
23
  export { token } from "./token.js";
22
24
  export { VisaManager, } from "./VisaManager.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EAGN,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EAEnB,4BAA4B,GAC5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,kBAAkB,EAClB,kBAAkB,EAElB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAwB,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EAEN,UAAU,EAGV,SAAS,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACN,SAAS,EACT,OAAO,IAAI,IAAI,EACf,OAAO,EACP,OAAO,GACP,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAwC,KAAK,EAAE,MAAM,YAAY,CAAC;AAYzE,OAAO,EAGN,WAAW,GACX,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EAGN,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EAEnB,4BAA4B,GAC5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,kBAAkB,EAClB,kBAAkB,EAElB,eAAe,EACf,kBAAkB,EAClB,aAAa,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAwB,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EAEN,UAAU,EAGV,SAAS,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EAKN,eAAe,EACf,SAAS,EAET,SAAS,GACT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACN,SAAS,EACT,OAAO,IAAI,IAAI,EACf,OAAO,EACP,OAAO,GACP,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAGN,UAAU,GAEV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAwC,KAAK,EAAE,MAAM,YAAY,CAAC;AAYzE,OAAO,EAGN,WAAW,GACX,MAAM,kBAAkB,CAAC"}
@@ -24,9 +24,11 @@ export declare function revoke(request: Record<string, unknown>, credentials: Cl
24
24
  * What a resource server calls on every request — so it reads the store
25
25
  * directly instead of going through introspection's client authentication.
26
26
  */
27
- export declare function verifyAccessToken(presented: string, store: VisaStore, now?: Date): Promise<{
27
+ export declare function verifyAccessToken(presented: string, store: Pick<VisaStore, "findAccessToken">, now?: Date): Promise<{
28
28
  clientId: string;
29
29
  userId?: string;
30
30
  scopes: string[];
31
+ expiresAt: Date;
32
+ lastUsedAt?: Date;
31
33
  } | null>;
32
34
  //# sourceMappingURL=introspect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../src/introspect.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAG1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAKxD,wBAAsB,UAAU,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,WAAW,EAAE,iBAAiB,EAC9B,KAAK,EAAE,SAAS,EAChB,GAAG,GAAE,IAAiB,EACtB,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,qBAAqB,CAAC,CAsDhC;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,WAAW,EAAE,iBAAiB,EAC9B,KAAK,EAAE,SAAS,EAChB,GAAG,GAAE,IAAiB,GACpB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACtC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,SAAS,EAChB,GAAG,GAAE,IAAiB,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAAC,CASzE"}
1
+ {"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../src/introspect.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAG1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAKxD,wBAAsB,UAAU,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,WAAW,EAAE,iBAAiB,EAC9B,KAAK,EAAE,SAAS,EAChB,GAAG,GAAE,IAAiB,EACtB,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,qBAAqB,CAAC,CAsDhC;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,WAAW,EAAE,iBAAiB,EAC9B,KAAK,EAAE,SAAS,EAChB,GAAG,GAAE,IAAiB,GACpB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACtC,SAAS,EAAE,MAAM,EAGjB,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,EACzC,GAAG,GAAE,IAAiB,GACpB,OAAO,CAAC;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,CAAC,EAAE,IAAI,CAAC;CAClB,GAAG,IAAI,CAAC,CAeR"}
@@ -92,7 +92,10 @@ export async function revoke(request, credentials, store, now = new Date()) {
92
92
  * What a resource server calls on every request — so it reads the store
93
93
  * directly instead of going through introspection's client authentication.
94
94
  */
95
- export async function verifyAccessToken(presented, store, now = new Date()) {
95
+ export async function verifyAccessToken(presented,
96
+ // Only the one read: a resource server that keeps nothing but tokens should
97
+ // not have to implement eleven methods it never calls.
98
+ store, now = new Date()) {
96
99
  const token = await store.findAccessToken(hashSecret(presented));
97
100
  if (token === null)
98
101
  return null;
@@ -102,6 +105,12 @@ export async function verifyAccessToken(presented, store, now = new Date()) {
102
105
  clientId: token.clientId,
103
106
  ...(token.userId === undefined ? {} : { userId: token.userId }),
104
107
  scopes: token.scopes,
108
+ // Carried out so a caller can report the expiry it just checked, rather
109
+ // than reading the store a second time to say the same thing.
110
+ expiresAt: token.expiresAt,
111
+ // The PREVIOUS use, before this one is recorded — which is what the
112
+ // upstream guard shows on `currentAccessToken.lastUsedAt`.
113
+ ...(token.lastUsedAt === undefined ? {} : { lastUsedAt: token.lastUsedAt }),
105
114
  };
106
115
  }
107
116
  function asString(value) {