@c9up/visa 0.1.0 → 0.1.2
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/dist/VisaManager.d.ts +40 -7
- package/dist/VisaManager.d.ts.map +1 -1
- package/dist/VisaManager.js +79 -3
- package/dist/VisaManager.js.map +1 -1
- package/dist/VisaProvider.d.ts.map +1 -1
- package/dist/VisaProvider.js +56 -0
- package/dist/VisaProvider.js.map +1 -1
- package/dist/authorize.d.ts +18 -0
- package/dist/authorize.d.ts.map +1 -1
- package/dist/authorize.js +6 -0
- package/dist/authorize.js.map +1 -1
- package/dist/config.d.ts +35 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +19 -0
- package/dist/configure.js.map +1 -1
- package/dist/errors.d.ts +9 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/guard.d.ts +243 -0
- package/dist/guard.d.ts.map +1 -0
- package/dist/guard.js +293 -0
- package/dist/guard.js.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/introspect.d.ts +11 -1
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +27 -1
- package/dist/introspect.js.map +1 -1
- package/dist/protectedResource.d.ts +81 -0
- package/dist/protectedResource.d.ts.map +1 -0
- package/dist/protectedResource.js +124 -0
- package/dist/protectedResource.js.map +1 -0
- package/dist/register.d.ts +96 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +173 -0
- package/dist/register.js.map +1 -0
- package/dist/resource.d.ts +50 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +101 -0
- package/dist/resource.js.map +1 -0
- package/dist/store.d.ts +33 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/stores/atlas.d.ts +106 -0
- package/dist/stores/atlas.d.ts.map +1 -0
- package/dist/stores/atlas.js +408 -0
- package/dist/stores/atlas.js.map +1 -0
- package/dist/stores/memory.d.ts +5 -0
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +30 -0
- package/dist/stores/memory.js.map +1 -1
- package/dist/token.d.ts +2 -0
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +21 -1
- package/dist/token.js.map +1 -1
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -1
- package/src/VisaManager.ts +109 -3
- package/src/VisaProvider.ts +64 -0
- package/src/authorize.ts +27 -0
- package/src/config.ts +35 -0
- package/src/configure.ts +21 -0
- package/src/errors.ts +16 -2
- package/src/guard.ts +443 -0
- package/src/index.ts +41 -0
- package/src/introspect.ts +33 -2
- package/src/protectedResource.ts +171 -0
- package/src/register.ts +296 -0
- package/src/resource.ts +123 -0
- package/src/store.ts +38 -0
- package/src/stores/atlas.ts +514 -0
- package/src/stores/memory.ts +38 -0
- package/src/token.ts +45 -1
- package/src/types.ts +50 -0
- package/stubs/database/migrations/9000_create_visa_tables.stub +112 -0
package/src/types.ts
CHANGED
|
@@ -57,6 +57,13 @@ export interface AuthorizationCode {
|
|
|
57
57
|
expiresAt: Date;
|
|
58
58
|
/** Set the moment it is exchanged, so a replay is detectable (§7.5.1). */
|
|
59
59
|
consumedAt?: Date;
|
|
60
|
+
/**
|
|
61
|
+
* The resources this code may buy a token for (RFC 8707).
|
|
62
|
+
*
|
|
63
|
+
* Bound here rather than at the token endpoint: the user saw the request
|
|
64
|
+
* that named them, and the exchange may narrow the list but never widen it.
|
|
65
|
+
*/
|
|
66
|
+
resources?: string[];
|
|
60
67
|
/** Carried through to the id_token when OIDC lands. */
|
|
61
68
|
nonce?: string;
|
|
62
69
|
}
|
|
@@ -69,6 +76,22 @@ export interface AccessToken {
|
|
|
69
76
|
scopes: string[];
|
|
70
77
|
expiresAt: Date;
|
|
71
78
|
revokedAt?: Date;
|
|
79
|
+
/**
|
|
80
|
+
* When this token was last presented, if the store records it.
|
|
81
|
+
*
|
|
82
|
+
* What an "Applications connectées" screen shows next to each authorisation,
|
|
83
|
+
* and what tells a dormant token from a live one.
|
|
84
|
+
*/
|
|
85
|
+
lastUsedAt?: Date;
|
|
86
|
+
/**
|
|
87
|
+
* Which resources this token is for (RFC 8707).
|
|
88
|
+
*
|
|
89
|
+
* Absent means unbound — a token for everything, which is what every token
|
|
90
|
+
* was before a client started asking. A resource server checks it against
|
|
91
|
+
* its own identifier, so a token that travelled to the wrong one is refused
|
|
92
|
+
* there instead of being honoured.
|
|
93
|
+
*/
|
|
94
|
+
audience?: string[];
|
|
72
95
|
/**
|
|
73
96
|
* The refresh family this token was minted in, when there is one.
|
|
74
97
|
*
|
|
@@ -95,6 +118,8 @@ export interface RefreshToken {
|
|
|
95
118
|
expiresAt: Date;
|
|
96
119
|
consumedAt?: Date;
|
|
97
120
|
revokedAt?: Date;
|
|
121
|
+
/** The resources a token minted from this one may be for (RFC 8707). */
|
|
122
|
+
resources?: string[];
|
|
98
123
|
}
|
|
99
124
|
|
|
100
125
|
/** What a user agreed to give a client, so they are asked once. */
|
|
@@ -105,6 +130,29 @@ export interface Consent {
|
|
|
105
130
|
grantedAt: Date;
|
|
106
131
|
}
|
|
107
132
|
|
|
133
|
+
/**
|
|
134
|
+
* One application a user has authorised, as a "connected applications" screen
|
|
135
|
+
* shows it.
|
|
136
|
+
*
|
|
137
|
+
* The unit is the APPLICATION, not the token: a person authorised an
|
|
138
|
+
* application, and the tokens come and go under that one decision. Upstream's
|
|
139
|
+
* equivalent listing is per token because it has no clients; here a user with
|
|
140
|
+
* four live tokens for one application has authorised one thing, and seeing
|
|
141
|
+
* four rows would be a lie about what revoking does.
|
|
142
|
+
*/
|
|
143
|
+
export interface AuthorizedApplication {
|
|
144
|
+
clientId: string;
|
|
145
|
+
/** The client's registered name, or its id when the client is gone. */
|
|
146
|
+
name: string;
|
|
147
|
+
/** What was granted — the consent, not what any one token happens to carry. */
|
|
148
|
+
scopes: string[];
|
|
149
|
+
grantedAt: Date;
|
|
150
|
+
/** The most recent use across this application's tokens, if any was used. */
|
|
151
|
+
lastUsedAt?: Date;
|
|
152
|
+
/** Whether anything issued to it is still usable right now. */
|
|
153
|
+
active: boolean;
|
|
154
|
+
}
|
|
155
|
+
|
|
108
156
|
/** The token endpoint's success body (§4.1.4). */
|
|
109
157
|
export interface TokenResponse {
|
|
110
158
|
access_token: string;
|
|
@@ -131,4 +179,6 @@ export interface IntrospectionResponse {
|
|
|
131
179
|
exp?: number;
|
|
132
180
|
iat?: number;
|
|
133
181
|
token_type?: "Bearer";
|
|
182
|
+
/** Which resources the token is for — RFC 8707 §3 names introspection as where this travels. */
|
|
183
|
+
aud?: string[];
|
|
134
184
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: database/migrations/9000_create_visa_tables.ts
|
|
3
|
+
---
|
|
4
|
+
import { Migration } from "@c9up/atlas";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What the authorization server keeps.
|
|
8
|
+
*
|
|
9
|
+
* Every credential column holds a HASH, never the value that was handed out —
|
|
10
|
+
* a dump of these tables is not a set of working tokens.
|
|
11
|
+
*
|
|
12
|
+
* Two portability rules are followed deliberately and should survive an edit:
|
|
13
|
+
*
|
|
14
|
+
* No `t.timestamps()`. It emits `DEFAULT (NOW())`, which is not a sqlite
|
|
15
|
+
* function and fails at `migration:run` on a fresh sqlite app. Every timestamp
|
|
16
|
+
* here is written explicitly by the store instead.
|
|
17
|
+
*
|
|
18
|
+
* Every string that is a primary key or an index stays well inside MySQL's
|
|
19
|
+
* InnoDB utf8mb4 limit of 3072 bytes (768 characters). A hash is 64 characters
|
|
20
|
+
* of hex; an identifier is capped at 255.
|
|
21
|
+
*/
|
|
22
|
+
export default class CreateVisaTables extends Migration {
|
|
23
|
+
async up() {
|
|
24
|
+
this.schema.createTable("visa_clients", (t) => {
|
|
25
|
+
t.string("id", 255).primary();
|
|
26
|
+
t.string("name", 255).notNullable();
|
|
27
|
+
// Null for a public client — a SPA or a native app has no secret to keep.
|
|
28
|
+
t.string("secret_hash", 255).nullable();
|
|
29
|
+
t.json("redirect_uris").notNullable();
|
|
30
|
+
t.json("grant_types").notNullable();
|
|
31
|
+
t.json("scopes").notNullable();
|
|
32
|
+
t.string("token_endpoint_auth_method", 32).notNullable();
|
|
33
|
+
t.boolean("trusted").notNullable();
|
|
34
|
+
t.timestamp("created_at").notNullable();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.schema.createTable("visa_authorization_codes", (t) => {
|
|
38
|
+
t.string("code_hash", 64).primary();
|
|
39
|
+
t.string("client_id", 255).notNullable();
|
|
40
|
+
t.string("user_id", 255).notNullable();
|
|
41
|
+
t.text("redirect_uri").notNullable();
|
|
42
|
+
t.json("scopes").notNullable();
|
|
43
|
+
t.string("code_challenge", 128).notNullable();
|
|
44
|
+
t.string("code_challenge_method", 10).notNullable();
|
|
45
|
+
t.timestamp("expires_at").notNullable();
|
|
46
|
+
// Set the moment it is exchanged: the single-use guarantee is an
|
|
47
|
+
// UPDATE guarded on this column being null.
|
|
48
|
+
t.timestamp("consumed_at").nullable();
|
|
49
|
+
t.string("nonce", 255).nullable();
|
|
50
|
+
// RFC 8707 — which resources a token bought with this code may be
|
|
51
|
+
// for. Empty means unbound, which is every code a client sent no
|
|
52
|
+
// `resource` with.
|
|
53
|
+
t.json("resources").notNullable();
|
|
54
|
+
t.timestamp("created_at").notNullable();
|
|
55
|
+
t.index("user_id");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.schema.createTable("visa_access_tokens", (t) => {
|
|
59
|
+
t.string("token_hash", 64).primary();
|
|
60
|
+
t.string("client_id", 255).notNullable();
|
|
61
|
+
// Null for `client_credentials`: there is no user behind that grant.
|
|
62
|
+
t.string("user_id", 255).nullable();
|
|
63
|
+
t.json("scopes").notNullable();
|
|
64
|
+
t.timestamp("expires_at").notNullable();
|
|
65
|
+
t.timestamp("revoked_at").nullable();
|
|
66
|
+
t.timestamp("last_used_at").nullable();
|
|
67
|
+
// The refresh family this token was minted in. Indexed because
|
|
68
|
+
// revoking a leaked family reads every token by it.
|
|
69
|
+
t.string("family_id", 64).nullable();
|
|
70
|
+
// RFC 8707 — what this token is for. Empty means unbound, and a
|
|
71
|
+
// resource server accepts it; bound, it is refused anywhere else.
|
|
72
|
+
t.json("audience").notNullable();
|
|
73
|
+
t.timestamp("created_at").notNullable();
|
|
74
|
+
t.index("family_id");
|
|
75
|
+
t.index("user_id");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.schema.createTable("visa_refresh_tokens", (t) => {
|
|
79
|
+
t.string("token_hash", 64).primary();
|
|
80
|
+
t.string("family_id", 64).notNullable();
|
|
81
|
+
t.string("client_id", 255).notNullable();
|
|
82
|
+
t.string("user_id", 255).nullable();
|
|
83
|
+
t.json("scopes").notNullable();
|
|
84
|
+
t.timestamp("expires_at").notNullable();
|
|
85
|
+
t.timestamp("consumed_at").nullable();
|
|
86
|
+
t.timestamp("revoked_at").nullable();
|
|
87
|
+
// Carried so a rotation narrows from the same list the code bound.
|
|
88
|
+
t.json("resources").notNullable();
|
|
89
|
+
t.timestamp("created_at").notNullable();
|
|
90
|
+
t.index("family_id");
|
|
91
|
+
t.index("user_id");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.schema.createTable("visa_consents", (t) => {
|
|
95
|
+
t.string("user_id", 255).notNullable();
|
|
96
|
+
t.string("client_id", 255).notNullable();
|
|
97
|
+
t.json("scopes").notNullable();
|
|
98
|
+
t.timestamp("granted_at").notNullable();
|
|
99
|
+
// One consent per pair: asking twice for the same thing is the bug
|
|
100
|
+
// this table exists to prevent.
|
|
101
|
+
t.primary(["user_id", "client_id"]);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async down() {
|
|
106
|
+
this.schema.dropTable("visa_consents");
|
|
107
|
+
this.schema.dropTable("visa_refresh_tokens");
|
|
108
|
+
this.schema.dropTable("visa_access_tokens");
|
|
109
|
+
this.schema.dropTable("visa_authorization_codes");
|
|
110
|
+
this.schema.dropTable("visa_clients");
|
|
111
|
+
}
|
|
112
|
+
}
|