@c9up/visa 0.1.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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/VisaManager.d.ts +94 -0
  4. package/dist/VisaManager.d.ts.map +1 -0
  5. package/dist/VisaManager.js +216 -0
  6. package/dist/VisaManager.js.map +1 -0
  7. package/dist/VisaProvider.d.ts +41 -0
  8. package/dist/VisaProvider.d.ts.map +1 -0
  9. package/dist/VisaProvider.js +153 -0
  10. package/dist/VisaProvider.js.map +1 -0
  11. package/dist/augmentations.d.ts +25 -0
  12. package/dist/augmentations.d.ts.map +1 -0
  13. package/dist/augmentations.js +19 -0
  14. package/dist/augmentations.js.map +1 -0
  15. package/dist/authorize.d.ts +78 -0
  16. package/dist/authorize.d.ts.map +1 -0
  17. package/dist/authorize.js +149 -0
  18. package/dist/authorize.js.map +1 -0
  19. package/dist/clients.d.ts +70 -0
  20. package/dist/clients.d.ts.map +1 -0
  21. package/dist/clients.js +177 -0
  22. package/dist/clients.js.map +1 -0
  23. package/dist/config.d.ts +30 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +8 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/configure.d.ts +28 -0
  28. package/dist/configure.d.ts.map +1 -0
  29. package/dist/configure.js +24 -0
  30. package/dist/configure.js.map +1 -0
  31. package/dist/crypto.d.ts +35 -0
  32. package/dist/crypto.d.ts.map +1 -0
  33. package/dist/crypto.js +56 -0
  34. package/dist/crypto.js.map +1 -0
  35. package/dist/errors.d.ts +47 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +50 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/index.d.ts +25 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +23 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/introspect.d.ts +32 -0
  44. package/dist/introspect.d.ts.map +1 -0
  45. package/dist/introspect.js +110 -0
  46. package/dist/introspect.js.map +1 -0
  47. package/dist/services/main.d.ts +29 -0
  48. package/dist/services/main.d.ts.map +1 -0
  49. package/dist/services/main.js +48 -0
  50. package/dist/services/main.js.map +1 -0
  51. package/dist/store.d.ts +44 -0
  52. package/dist/store.d.ts.map +1 -0
  53. package/dist/store.js +12 -0
  54. package/dist/store.js.map +1 -0
  55. package/dist/stores/memory.d.ts +34 -0
  56. package/dist/stores/memory.d.ts.map +1 -0
  57. package/dist/stores/memory.js +100 -0
  58. package/dist/stores/memory.js.map +1 -0
  59. package/dist/stubs.d.ts +14 -0
  60. package/dist/stubs.d.ts.map +1 -0
  61. package/dist/stubs.js +15 -0
  62. package/dist/stubs.js.map +1 -0
  63. package/dist/testing/main.d.ts +49 -0
  64. package/dist/testing/main.d.ts.map +1 -0
  65. package/dist/testing/main.js +77 -0
  66. package/dist/testing/main.js.map +1 -0
  67. package/dist/token.d.ts +20 -0
  68. package/dist/token.d.ts.map +1 -0
  69. package/dist/token.js +212 -0
  70. package/dist/token.js.map +1 -0
  71. package/dist/types.d.ts +123 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +9 -0
  74. package/dist/types.js.map +1 -0
  75. package/package.json +83 -0
  76. package/src/VisaManager.ts +346 -0
  77. package/src/VisaProvider.ts +197 -0
  78. package/src/augmentations.ts +29 -0
  79. package/src/authorize.ts +250 -0
  80. package/src/clients.ts +231 -0
  81. package/src/config.ts +34 -0
  82. package/src/configure.ts +42 -0
  83. package/src/crypto.ts +59 -0
  84. package/src/errors.ts +79 -0
  85. package/src/index.ts +67 -0
  86. package/src/introspect.ts +139 -0
  87. package/src/services/main.ts +57 -0
  88. package/src/store.ts +56 -0
  89. package/src/stores/memory.ts +118 -0
  90. package/src/stubs.ts +16 -0
  91. package/src/testing/main.ts +119 -0
  92. package/src/token.ts +325 -0
  93. package/src/types.ts +134 -0
  94. package/stubs/config/visa.stub +25 -0
package/dist/token.js ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * The token endpoint — the three grants OAuth 2.1 keeps.
3
+ *
4
+ * `password` and `implicit` are gone from the spec and are not implemented
5
+ * here under any option: both hand a credential to a party that should never
6
+ * hold one, and an application that needs them needs a different design.
7
+ */
8
+ import { assertGrantAllowed, authenticateClient, resolveScopes, } from "./clients.js";
9
+ import { hashMatches, hashSecret, randomToken, sha256 } from "./crypto.js";
10
+ import { OAuthError } from "./errors.js";
11
+ const DEFAULTS = {
12
+ accessTokenTtlSeconds: 3600,
13
+ refreshTokenTtlSeconds: 60 * 60 * 24 * 30,
14
+ issueRefreshTokens: true,
15
+ };
16
+ export async function token(request, credentials, store, options = {}, now = new Date()) {
17
+ const grant = asString(request.grant_type);
18
+ if (grant === undefined) {
19
+ throw new OAuthError("invalid_request", "grant_type is required.");
20
+ }
21
+ // Authenticate BEFORE looking at the grant: an unknown grant from an
22
+ // unauthenticated caller must not be distinguishable from an unknown grant
23
+ // from a real client.
24
+ const client = await authenticateClient(credentials, (id) => store.findClient(id));
25
+ if (!isKnownGrant(grant)) {
26
+ throw new OAuthError("unsupported_grant_type", grant === "password" || grant === "implicit"
27
+ ? `The ${grant} grant was removed in OAuth 2.1.`
28
+ : undefined);
29
+ }
30
+ assertGrantAllowed(client, grant);
31
+ if (grant === "authorization_code") {
32
+ return exchangeCode(request, client, store, options, now);
33
+ }
34
+ if (grant === "refresh_token") {
35
+ return exchangeRefresh(request, client, store, options, now);
36
+ }
37
+ return clientCredentials(request, client, store, options, now);
38
+ }
39
+ function isKnownGrant(grant) {
40
+ return (grant === "authorization_code" ||
41
+ grant === "refresh_token" ||
42
+ grant === "client_credentials");
43
+ }
44
+ /**
45
+ * The authorization code grant.
46
+ *
47
+ * Four things are verified, and each one has a published attack behind it:
48
+ * the code belongs to THIS client (otherwise a malicious client redeems
49
+ * another's code), the redirect URI is the one it was issued for (code
50
+ * injection), the PKCE verifier hashes to the stored challenge (interception
51
+ * on a native app), and the code has not been used (replay).
52
+ */
53
+ async function exchangeCode(request, client, store, options, now) {
54
+ const code = asString(request.code);
55
+ const verifier = asString(request.code_verifier);
56
+ if (code === undefined) {
57
+ throw new OAuthError("invalid_request", "code is required.");
58
+ }
59
+ if (verifier === undefined) {
60
+ throw new OAuthError("invalid_request", "code_verifier is required.");
61
+ }
62
+ const record = await store.findAuthorizationCode(hashSecret(code));
63
+ // One sentence for every failure below: "no such code", "expired",
64
+ // "already used" and "wrong client" are indistinguishable to the caller.
65
+ const invalid = new OAuthError("invalid_grant", "The code is not valid.");
66
+ if (record === null || record.clientId !== client.id)
67
+ throw invalid;
68
+ if (record.expiresAt <= now)
69
+ throw invalid;
70
+ if (record.consumedAt !== undefined) {
71
+ // A code coming back a second time means it leaked. Anything it already
72
+ // bought has to go with it — the legitimate exchange has happened, so
73
+ // the tokens in flight are the attacker's or a race.
74
+ await store.revokeFamily(codeFamily(record.codeHash), now);
75
+ throw invalid;
76
+ }
77
+ const redirectUri = asString(request.redirect_uri);
78
+ // Required whenever it was in the authorization request, which this server
79
+ // always records.
80
+ if (redirectUri !== record.redirectUri)
81
+ throw invalid;
82
+ if (!verifierMatches(verifier, record.codeChallenge, record.codeChallengeMethod)) {
83
+ throw invalid;
84
+ }
85
+ // The single-use gate. Atomic in the store, so two requests racing with the
86
+ // same code cannot both pass.
87
+ if (!(await store.consumeAuthorizationCode(record.codeHash, now))) {
88
+ throw invalid;
89
+ }
90
+ return issue({
91
+ client,
92
+ userId: record.userId,
93
+ scopes: record.scopes,
94
+ familyId: codeFamily(record.codeHash),
95
+ }, store, options, now);
96
+ }
97
+ /**
98
+ * The family a code's tokens belong to.
99
+ *
100
+ * Derived from the code rather than random, so the tokens minted from a code
101
+ * can be revoked when that same code is replayed — at which point the code
102
+ * record is all we have to go on.
103
+ */
104
+ function codeFamily(codeHash) {
105
+ return sha256(`family:${codeHash}`);
106
+ }
107
+ /** PKCE verification (RFC 7636 §4.6). */
108
+ function verifierMatches(verifier, challenge, method) {
109
+ const computed = method === "S256" ? sha256(verifier) : verifier;
110
+ return hashMatches(computed, challenge);
111
+ }
112
+ /**
113
+ * The refresh grant, with rotation.
114
+ *
115
+ * Every use mints a new refresh token and consumes the old one. A consumed
116
+ * token coming back is a replay — the legitimate holder has already moved on
117
+ * — and since there is no way to tell the thief from the victim, the whole
118
+ * family is revoked and both are signed out (RFC 9700 §4.14.2).
119
+ */
120
+ async function exchangeRefresh(request, client, store, options, now) {
121
+ const presented = asString(request.refresh_token);
122
+ if (presented === undefined) {
123
+ throw new OAuthError("invalid_request", "refresh_token is required.");
124
+ }
125
+ const record = await store.findRefreshToken(hashSecret(presented));
126
+ const invalid = new OAuthError("invalid_grant", "The refresh token is not valid.");
127
+ if (record === null || record.clientId !== client.id)
128
+ throw invalid;
129
+ if (record.revokedAt !== undefined)
130
+ throw invalid;
131
+ if (record.consumedAt !== undefined) {
132
+ await store.revokeFamily(record.familyId, now);
133
+ throw invalid;
134
+ }
135
+ if (record.expiresAt <= now)
136
+ throw invalid;
137
+ // A refresh MUST NOT widen what was consented to (§4.3.1); it may narrow.
138
+ const requested = asString(request.scope);
139
+ const scopes = requested === undefined ? record.scopes : narrow(requested, record.scopes);
140
+ if (!(await store.consumeRefreshToken(record.tokenHash, now)))
141
+ throw invalid;
142
+ return issue({
143
+ client,
144
+ ...(record.userId === undefined ? {} : { userId: record.userId }),
145
+ scopes,
146
+ familyId: record.familyId,
147
+ }, store, options, now);
148
+ }
149
+ function narrow(requested, granted) {
150
+ const asked = requested.split(/\s+/).filter((scope) => scope !== "");
151
+ const extra = asked.filter((scope) => !granted.includes(scope));
152
+ if (extra.length > 0) {
153
+ throw new OAuthError("invalid_scope", `Not part of the original grant: ${extra.join(" ")}`);
154
+ }
155
+ return asked;
156
+ }
157
+ /**
158
+ * The client credentials grant — the client acting as itself.
159
+ *
160
+ * No user, so no refresh token: there is nothing to keep alive on someone's
161
+ * behalf, and the client can always ask again with the credentials it holds
162
+ * (§4.2.3).
163
+ */
164
+ async function clientCredentials(request, client, store, options, now) {
165
+ if (client.tokenEndpointAuthMethod === "none") {
166
+ // A public client has no secret, so "the client itself" is anyone who
167
+ // read its id out of a browser.
168
+ throw new OAuthError("unauthorized_client", "A public client cannot use the client_credentials grant.");
169
+ }
170
+ const scopes = resolveScopes(asString(request.scope), client);
171
+ return issue({ client, scopes }, store, { ...options, issueRefreshTokens: false }, now);
172
+ }
173
+ /** Mint the pair and store both, hashed. */
174
+ async function issue(grant, store, options, now) {
175
+ const accessTtl = options.accessTokenTtlSeconds ?? DEFAULTS.accessTokenTtlSeconds;
176
+ const accessToken = randomToken();
177
+ await store.saveAccessToken({
178
+ tokenHash: hashSecret(accessToken),
179
+ clientId: grant.client.id,
180
+ ...(grant.userId === undefined ? {} : { userId: grant.userId }),
181
+ scopes: grant.scopes,
182
+ expiresAt: new Date(now.getTime() + accessTtl * 1000),
183
+ ...(grant.familyId === undefined ? {} : { familyId: grant.familyId }),
184
+ });
185
+ const response = {
186
+ access_token: accessToken,
187
+ token_type: "Bearer",
188
+ expires_in: accessTtl,
189
+ scope: grant.scopes.join(" "),
190
+ };
191
+ const wantsRefresh = (options.issueRefreshTokens ?? DEFAULTS.issueRefreshTokens) &&
192
+ grant.familyId !== undefined &&
193
+ grant.client.grantTypes.includes("refresh_token");
194
+ if (!wantsRefresh || grant.familyId === undefined)
195
+ return response;
196
+ const refreshTtl = options.refreshTokenTtlSeconds ?? DEFAULTS.refreshTokenTtlSeconds;
197
+ const refreshToken = randomToken();
198
+ await store.saveRefreshToken({
199
+ tokenHash: hashSecret(refreshToken),
200
+ familyId: grant.familyId,
201
+ clientId: grant.client.id,
202
+ ...(grant.userId === undefined ? {} : { userId: grant.userId }),
203
+ scopes: grant.scopes,
204
+ expiresAt: new Date(now.getTime() + refreshTtl * 1000),
205
+ });
206
+ response.refresh_token = refreshToken;
207
+ return response;
208
+ }
209
+ function asString(value) {
210
+ return typeof value === "string" && value !== "" ? value : undefined;
211
+ }
212
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACN,kBAAkB,EAClB,kBAAkB,EAElB,aAAa,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAWzC,MAAM,QAAQ,GAAG;IAChB,qBAAqB,EAAE,IAAI;IAC3B,sBAAsB,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;IACzC,kBAAkB,EAAE,IAAI;CACxB,CAAC;AAKF,MAAM,CAAC,KAAK,UAAU,KAAK,CAC1B,OAAqB,EACrB,WAA8B,EAC9B,KAAgB,EAChB,UAAwB,EAAE,EAC1B,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,yBAAyB,CAAC,CAAC;IACpE,CAAC;IACD,qEAAqE;IACrE,2EAA2E;IAC3E,sBAAsB;IACtB,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,CAC3D,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CACpB,CAAC;IAEF,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,UAAU,CACnB,wBAAwB,EACxB,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK,UAAU;YAC3C,CAAC,CAAC,OAAO,KAAK,kCAAkC;YAChD,CAAC,CAAC,SAAS,CACZ,CAAC;IACH,CAAC;IACD,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAElC,IAAI,KAAK,KAAK,oBAAoB,EAAE,CAAC;QACpC,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;QAC/B,OAAO,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IAClC,OAAO,CACN,KAAK,KAAK,oBAAoB;QAC9B,KAAK,KAAK,eAAe;QACzB,KAAK,KAAK,oBAAoB,CAC9B,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,YAAY,CAC1B,OAAqB,EACrB,MAAc,EACd,KAAgB,EAChB,OAAqB,EACrB,GAAS;IAET,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACjD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,4BAA4B,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,qBAAqB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACnE,mEAAmE;IACnE,yEAAyE;IACzE,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,eAAe,EAAE,wBAAwB,CAAC,CAAC;IAC1E,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE;QAAE,MAAM,OAAO,CAAC;IACpE,IAAI,MAAM,CAAC,SAAS,IAAI,GAAG;QAAE,MAAM,OAAO,CAAC;IAE3C,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACrC,wEAAwE;QACxE,sEAAsE;QACtE,qDAAqD;QACrD,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC;QAC3D,MAAM,OAAO,CAAC;IACf,CAAC;IAED,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACnD,2EAA2E;IAC3E,kBAAkB;IAClB,IAAI,WAAW,KAAK,MAAM,CAAC,WAAW;QAAE,MAAM,OAAO,CAAC;IAEtD,IACC,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,mBAAmB,CAAC,EAC3E,CAAC;QACF,MAAM,OAAO,CAAC;IACf,CAAC;IAED,4EAA4E;IAC5E,8BAA8B;IAC9B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;QACnE,MAAM,OAAO,CAAC;IACf,CAAC;IAED,OAAO,KAAK,CACX;QACC,MAAM;QACN,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC;KACrC,EACD,KAAK,EACL,OAAO,EACP,GAAG,CACH,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,UAAU,CAAC,QAAgB;IACnC,OAAO,MAAM,CAAC,UAAU,QAAQ,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,yCAAyC;AACzC,SAAS,eAAe,CACvB,QAAgB,EAChB,SAAiB,EACjB,MAAwB;IAExB,MAAM,QAAQ,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACjE,OAAO,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,eAAe,CAC7B,OAAqB,EACrB,MAAc,EACd,KAAgB,EAChB,OAAqB,EACrB,GAAS;IAET,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAClD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,4BAA4B,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,IAAI,UAAU,CAC7B,eAAe,EACf,iCAAiC,CACjC,CAAC;IACF,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE;QAAE,MAAM,OAAO,CAAC;IACpE,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;QAAE,MAAM,OAAO,CAAC;IAElD,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAC/C,MAAM,OAAO,CAAC;IACf,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,IAAI,GAAG;QAAE,MAAM,OAAO,CAAC;IAE3C,0EAA0E;IAC1E,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,MAAM,GACX,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAE5E,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAAE,MAAM,OAAO,CAAC;IAE7E,OAAO,KAAK,CACX;QACC,MAAM;QACN,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QACjE,MAAM;QACN,QAAQ,EAAE,MAAM,CAAC,QAAQ;KACzB,EACD,KAAK,EACL,OAAO,EACP,GAAG,CACH,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,SAAiB,EAAE,OAA0B;IAC5D,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAChE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,UAAU,CACnB,eAAe,EACf,mCAAmC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACpD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,iBAAiB,CAC/B,OAAqB,EACrB,MAAc,EACd,KAAgB,EAChB,OAAqB,EACrB,GAAS;IAET,IAAI,MAAM,CAAC,uBAAuB,KAAK,MAAM,EAAE,CAAC;QAC/C,sEAAsE;QACtE,gCAAgC;QAChC,MAAM,IAAI,UAAU,CACnB,qBAAqB,EACrB,0DAA0D,CAC1D,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;IAC9D,OAAO,KAAK,CACX,EAAE,MAAM,EAAE,MAAM,EAAE,EAClB,KAAK,EACL,EAAE,GAAG,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,EACzC,GAAG,CACH,CAAC;AACH,CAAC;AASD,4CAA4C;AAC5C,KAAK,UAAU,KAAK,CACnB,KAAa,EACb,KAAgB,EAChB,OAAqB,EACrB,GAAS;IAET,MAAM,SAAS,GACd,OAAO,CAAC,qBAAqB,IAAI,QAAQ,CAAC,qBAAqB,CAAC;IACjE,MAAM,WAAW,GAAG,WAAW,EAAE,CAAC;IAElC,MAAM,KAAK,CAAC,eAAe,CAAC;QAC3B,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC;QAClC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE;QACzB,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QAC/D,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC;QACrD,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;KACrE,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAkB;QAC/B,YAAY,EAAE,WAAW;QACzB,UAAU,EAAE,QAAQ;QACpB,UAAU,EAAE,SAAS;QACrB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;KAC7B,CAAC;IAEF,MAAM,YAAY,GACjB,CAAC,OAAO,CAAC,kBAAkB,IAAI,QAAQ,CAAC,kBAAkB,CAAC;QAC3D,KAAK,CAAC,QAAQ,KAAK,SAAS;QAC5B,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IACnD,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAEnE,MAAM,UAAU,GACf,OAAO,CAAC,sBAAsB,IAAI,QAAQ,CAAC,sBAAsB,CAAC;IACnE,MAAM,YAAY,GAAG,WAAW,EAAE,CAAC;IACnC,MAAM,KAAK,CAAC,gBAAgB,CAAC;QAC5B,SAAS,EAAE,UAAU,CAAC,YAAY,CAAC;QACnC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE;QACzB,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QAC/D,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,UAAU,GAAG,IAAI,CAAC;KACtD,CAAC,CAAC;IACH,QAAQ,CAAC,aAAa,GAAG,YAAY,CAAC;IACtC,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACtE,CAAC"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * What an authorization server stores, and what it hands back.
3
+ *
4
+ * Nothing here is an entity of a particular ORM: the store contract takes and
5
+ * returns these shapes, so the same server runs on atlas, on Redis or on a
6
+ * table someone else owns.
7
+ */
8
+ /** How a client proves who it is at the token endpoint. */
9
+ export type ClientAuthMethod =
10
+ /** A confidential client with a secret, sent as HTTP Basic (§2.4.1). */
11
+ "client_secret_basic"
12
+ /** The same secret, in the form body. */
13
+ | "client_secret_post"
14
+ /** A public client — a SPA, a native app — which has no secret to keep. */
15
+ | "none";
16
+ export type GrantType = "authorization_code" | "refresh_token" | "client_credentials";
17
+ /**
18
+ * A registered application.
19
+ *
20
+ * `redirectUris` is a list of COMPLETE uris, path included: OAuth 2.1 requires
21
+ * an exact match, because a prefix match is how an open redirect on the
22
+ * client's domain turns into a stolen authorization code.
23
+ */
24
+ export interface Client {
25
+ id: string;
26
+ name: string;
27
+ /** Hashed. The plaintext is shown once, at registration, and never stored. */
28
+ secretHash?: string;
29
+ redirectUris: string[];
30
+ grantTypes: GrantType[];
31
+ /** What this client may ever ask for; a request narrows it, never widens it. */
32
+ scopes: string[];
33
+ tokenEndpointAuthMethod: ClientAuthMethod;
34
+ /** Skip the consent screen — for a first-party application you own. */
35
+ trusted?: boolean;
36
+ }
37
+ /**
38
+ * An authorization code: single use, short lived, bound to everything that
39
+ * was true when it was issued.
40
+ */
41
+ export interface AuthorizationCode {
42
+ /** Hashed, like every other credential this package persists. */
43
+ codeHash: string;
44
+ clientId: string;
45
+ userId: string;
46
+ redirectUri: string;
47
+ scopes: string[];
48
+ codeChallenge: string;
49
+ codeChallengeMethod: "S256" | "plain";
50
+ expiresAt: Date;
51
+ /** Set the moment it is exchanged, so a replay is detectable (§7.5.1). */
52
+ consumedAt?: Date;
53
+ /** Carried through to the id_token when OIDC lands. */
54
+ nonce?: string;
55
+ }
56
+ export interface AccessToken {
57
+ tokenHash: string;
58
+ clientId: string;
59
+ /** Absent for `client_credentials`: there is no user behind it. */
60
+ userId?: string;
61
+ scopes: string[];
62
+ expiresAt: Date;
63
+ revokedAt?: Date;
64
+ /**
65
+ * The refresh family this token was minted in, when there is one.
66
+ *
67
+ * Without it, revoking a leaked family would leave the access token it
68
+ * already bought alive for its full lifetime — which is most of what the
69
+ * revocation exists to prevent.
70
+ */
71
+ familyId?: string;
72
+ }
73
+ /**
74
+ * A refresh token, and the family it belongs to.
75
+ *
76
+ * `familyId` is what makes replay detection possible: rotation issues a new
77
+ * token in the same family, so presenting an old one proves a leak and the
78
+ * whole family goes (RFC 9700 §4.14.2).
79
+ */
80
+ export interface RefreshToken {
81
+ tokenHash: string;
82
+ familyId: string;
83
+ clientId: string;
84
+ userId?: string;
85
+ scopes: string[];
86
+ expiresAt: Date;
87
+ consumedAt?: Date;
88
+ revokedAt?: Date;
89
+ }
90
+ /** What a user agreed to give a client, so they are asked once. */
91
+ export interface Consent {
92
+ userId: string;
93
+ clientId: string;
94
+ scopes: string[];
95
+ grantedAt: Date;
96
+ }
97
+ /** The token endpoint's success body (§4.1.4). */
98
+ export interface TokenResponse {
99
+ access_token: string;
100
+ token_type: "Bearer";
101
+ expires_in: number;
102
+ refresh_token?: string;
103
+ scope?: string;
104
+ }
105
+ /** The introspection body (RFC 7662 §2.2). */
106
+ export interface IntrospectionResponse {
107
+ active: boolean;
108
+ /**
109
+ * Which authorization server vouches for this token.
110
+ *
111
+ * An opaque token carries nothing by itself; the issuer is a property of
112
+ * the SERVER, and this is where a caller gets to see it.
113
+ */
114
+ iss?: string;
115
+ scope?: string;
116
+ client_id?: string;
117
+ username?: string;
118
+ sub?: string;
119
+ exp?: number;
120
+ iat?: number;
121
+ token_type?: "Bearer";
122
+ }
123
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * What an authorization server stores, and what it hands back.
3
+ *
4
+ * Nothing here is an entity of a particular ORM: the store contract takes and
5
+ * returns these shapes, so the same server runs on atlas, on Redis or on a
6
+ * table someone else owns.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@c9up/visa",
3
+ "version": "0.1.0",
4
+ "description": "Visa — OAuth 2.1 authorization server for the Ream framework: authorization code with PKCE, refresh rotation, revocation and introspection, on a store contract",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./config": {
15
+ "types": "./dist/config.d.ts",
16
+ "import": "./dist/config.js"
17
+ },
18
+ "./provider": {
19
+ "types": "./dist/VisaProvider.d.ts",
20
+ "import": "./dist/VisaProvider.js"
21
+ },
22
+ "./configure": {
23
+ "types": "./dist/configure.d.ts",
24
+ "import": "./dist/configure.js"
25
+ },
26
+ "./services/main": {
27
+ "types": "./dist/services/main.d.ts",
28
+ "import": "./dist/services/main.js"
29
+ },
30
+ "./stores/memory": {
31
+ "types": "./dist/stores/memory.d.ts",
32
+ "import": "./dist/stores/memory.js"
33
+ },
34
+ "./types": {
35
+ "types": "./dist/types.d.ts",
36
+ "import": "./dist/types.js"
37
+ },
38
+ "./testing": {
39
+ "types": "./dist/testing/main.d.ts",
40
+ "import": "./dist/testing/main.js"
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "@biomejs/biome": "^2.5.12",
45
+ "@c9up/ream": "^0.2.25",
46
+ "@types/node": "^22.20.1",
47
+ "@vitest/coverage-v8": "4.1.11",
48
+ "typescript": "^6.0.3",
49
+ "vitest": "4.1.11"
50
+ },
51
+ "peerDependencies": {
52
+ "@c9up/ream": "^0.2.26"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "@c9up/ream": {
56
+ "optional": true
57
+ }
58
+ },
59
+ "engines": {
60
+ "node": ">=24.0.0"
61
+ },
62
+ "files": [
63
+ "src",
64
+ "dist",
65
+ "stubs",
66
+ "README.md",
67
+ "LICENSE"
68
+ ],
69
+ "publishConfig": {
70
+ "access": "public"
71
+ },
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "git+https://github.com/C9up/visa.git"
75
+ },
76
+ "scripts": {
77
+ "build": "tsc -p tsconfig.build.json",
78
+ "test": "vitest run",
79
+ "lint": "biome check src/ tests/",
80
+ "test:coverage": "vitest run --coverage",
81
+ "typecheck": "tsc --noEmit"
82
+ }
83
+ }