@c9up/transit 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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +191 -0
  3. package/dist/Oauth1Driver.d.ts +68 -0
  4. package/dist/Oauth1Driver.d.ts.map +1 -0
  5. package/dist/Oauth1Driver.js +148 -0
  6. package/dist/Oauth1Driver.js.map +1 -0
  7. package/dist/Oauth2Driver.d.ts +89 -0
  8. package/dist/Oauth2Driver.d.ts.map +1 -0
  9. package/dist/Oauth2Driver.js +196 -0
  10. package/dist/Oauth2Driver.js.map +1 -0
  11. package/dist/TransitManager.d.ts +59 -0
  12. package/dist/TransitManager.d.ts.map +1 -0
  13. package/dist/TransitManager.js +100 -0
  14. package/dist/TransitManager.js.map +1 -0
  15. package/dist/TransitProvider.d.ts +26 -0
  16. package/dist/TransitProvider.d.ts.map +1 -0
  17. package/dist/TransitProvider.js +32 -0
  18. package/dist/TransitProvider.js.map +1 -0
  19. package/dist/apple.d.ts +45 -0
  20. package/dist/apple.d.ts.map +1 -0
  21. package/dist/apple.js +80 -0
  22. package/dist/apple.js.map +1 -0
  23. package/dist/ber.d.ts +50 -0
  24. package/dist/ber.d.ts.map +1 -0
  25. package/dist/ber.js +156 -0
  26. package/dist/ber.js.map +1 -0
  27. package/dist/c14n.d.ts +38 -0
  28. package/dist/c14n.d.ts.map +1 -0
  29. package/dist/c14n.js +129 -0
  30. package/dist/c14n.js.map +1 -0
  31. package/dist/config.d.ts +115 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +146 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/configure.d.ts +18 -0
  36. package/dist/configure.d.ts.map +1 -0
  37. package/dist/configure.js +33 -0
  38. package/dist/configure.js.map +1 -0
  39. package/dist/drivers/AppleDriver.d.ts +36 -0
  40. package/dist/drivers/AppleDriver.d.ts.map +1 -0
  41. package/dist/drivers/AppleDriver.js +58 -0
  42. package/dist/drivers/AppleDriver.js.map +1 -0
  43. package/dist/drivers/DiscordDriver.d.ts +15 -0
  44. package/dist/drivers/DiscordDriver.d.ts.map +1 -0
  45. package/dist/drivers/DiscordDriver.js +45 -0
  46. package/dist/drivers/DiscordDriver.js.map +1 -0
  47. package/dist/drivers/FacebookDriver.d.ts +15 -0
  48. package/dist/drivers/FacebookDriver.d.ts.map +1 -0
  49. package/dist/drivers/FacebookDriver.js +47 -0
  50. package/dist/drivers/FacebookDriver.js.map +1 -0
  51. package/dist/drivers/GitHubDriver.d.ts +33 -0
  52. package/dist/drivers/GitHubDriver.d.ts.map +1 -0
  53. package/dist/drivers/GitHubDriver.js +72 -0
  54. package/dist/drivers/GitHubDriver.js.map +1 -0
  55. package/dist/drivers/GoogleDriver.d.ts +16 -0
  56. package/dist/drivers/GoogleDriver.d.ts.map +1 -0
  57. package/dist/drivers/GoogleDriver.js +31 -0
  58. package/dist/drivers/GoogleDriver.js.map +1 -0
  59. package/dist/drivers/LdapDirectory.d.ts +51 -0
  60. package/dist/drivers/LdapDirectory.d.ts.map +1 -0
  61. package/dist/drivers/LdapDirectory.js +127 -0
  62. package/dist/drivers/LdapDirectory.js.map +1 -0
  63. package/dist/drivers/LinkedInDriver.d.ts +29 -0
  64. package/dist/drivers/LinkedInDriver.d.ts.map +1 -0
  65. package/dist/drivers/LinkedInDriver.js +97 -0
  66. package/dist/drivers/LinkedInDriver.js.map +1 -0
  67. package/dist/drivers/LinkedInOpenidConnectDriver.d.ts +17 -0
  68. package/dist/drivers/LinkedInOpenidConnectDriver.d.ts.map +1 -0
  69. package/dist/drivers/LinkedInOpenidConnectDriver.js +27 -0
  70. package/dist/drivers/LinkedInOpenidConnectDriver.js.map +1 -0
  71. package/dist/drivers/OidcDriver.d.ts +64 -0
  72. package/dist/drivers/OidcDriver.d.ts.map +1 -0
  73. package/dist/drivers/OidcDriver.js +259 -0
  74. package/dist/drivers/OidcDriver.js.map +1 -0
  75. package/dist/drivers/SamlDriver.d.ts +66 -0
  76. package/dist/drivers/SamlDriver.d.ts.map +1 -0
  77. package/dist/drivers/SamlDriver.js +228 -0
  78. package/dist/drivers/SamlDriver.js.map +1 -0
  79. package/dist/drivers/SpotifyDriver.d.ts +16 -0
  80. package/dist/drivers/SpotifyDriver.d.ts.map +1 -0
  81. package/dist/drivers/SpotifyDriver.js +34 -0
  82. package/dist/drivers/SpotifyDriver.js.map +1 -0
  83. package/dist/drivers/TwitterDriver.d.ts +21 -0
  84. package/dist/drivers/TwitterDriver.d.ts.map +1 -0
  85. package/dist/drivers/TwitterDriver.js +46 -0
  86. package/dist/drivers/TwitterDriver.js.map +1 -0
  87. package/dist/drivers/TwitterXDriver.d.ts +22 -0
  88. package/dist/drivers/TwitterXDriver.d.ts.map +1 -0
  89. package/dist/drivers/TwitterXDriver.js +47 -0
  90. package/dist/drivers/TwitterXDriver.js.map +1 -0
  91. package/dist/dsig.d.ts +51 -0
  92. package/dist/dsig.d.ts.map +1 -0
  93. package/dist/dsig.js +273 -0
  94. package/dist/dsig.js.map +1 -0
  95. package/dist/index.d.ts +38 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +33 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/jwt.d.ts +135 -0
  100. package/dist/jwt.d.ts.map +1 -0
  101. package/dist/jwt.js +167 -0
  102. package/dist/jwt.js.map +1 -0
  103. package/dist/ldap.d.ts +82 -0
  104. package/dist/ldap.d.ts.map +1 -0
  105. package/dist/ldap.js +271 -0
  106. package/dist/ldap.js.map +1 -0
  107. package/dist/nodeEnv.d.ts +17 -0
  108. package/dist/nodeEnv.d.ts.map +1 -0
  109. package/dist/nodeEnv.js +33 -0
  110. package/dist/nodeEnv.js.map +1 -0
  111. package/dist/oauth1.d.ts +68 -0
  112. package/dist/oauth1.d.ts.map +1 -0
  113. package/dist/oauth1.js +106 -0
  114. package/dist/oauth1.js.map +1 -0
  115. package/dist/oidc.d.ts +61 -0
  116. package/dist/oidc.d.ts.map +1 -0
  117. package/dist/oidc.js +151 -0
  118. package/dist/oidc.js.map +1 -0
  119. package/dist/quasar.d.ts +14 -0
  120. package/dist/quasar.d.ts.map +1 -0
  121. package/dist/quasar.js +42 -0
  122. package/dist/quasar.js.map +1 -0
  123. package/dist/replay.d.ts +70 -0
  124. package/dist/replay.d.ts.map +1 -0
  125. package/dist/replay.js +82 -0
  126. package/dist/replay.js.map +1 -0
  127. package/dist/saml.d.ts +68 -0
  128. package/dist/saml.d.ts.map +1 -0
  129. package/dist/saml.js +181 -0
  130. package/dist/saml.js.map +1 -0
  131. package/dist/services/main.d.ts +22 -0
  132. package/dist/services/main.d.ts.map +1 -0
  133. package/dist/services/main.js +39 -0
  134. package/dist/services/main.js.map +1 -0
  135. package/dist/testing/FakeTransit.d.ts +82 -0
  136. package/dist/testing/FakeTransit.d.ts.map +1 -0
  137. package/dist/testing/FakeTransit.js +160 -0
  138. package/dist/testing/FakeTransit.js.map +1 -0
  139. package/dist/types.d.ts +118 -0
  140. package/dist/types.d.ts.map +1 -0
  141. package/dist/types.js +27 -0
  142. package/dist/types.js.map +1 -0
  143. package/dist/xml.d.ts +72 -0
  144. package/dist/xml.d.ts.map +1 -0
  145. package/dist/xml.js +353 -0
  146. package/dist/xml.js.map +1 -0
  147. package/package.json +73 -0
  148. package/src/Oauth1Driver.ts +216 -0
  149. package/src/Oauth2Driver.ts +271 -0
  150. package/src/TransitManager.ts +145 -0
  151. package/src/TransitProvider.ts +51 -0
  152. package/src/apple.ts +101 -0
  153. package/src/ber.ts +175 -0
  154. package/src/c14n.ts +187 -0
  155. package/src/config.ts +160 -0
  156. package/src/configure.ts +47 -0
  157. package/src/drivers/AppleDriver.ts +69 -0
  158. package/src/drivers/DiscordDriver.ts +50 -0
  159. package/src/drivers/FacebookDriver.ts +53 -0
  160. package/src/drivers/GitHubDriver.ts +91 -0
  161. package/src/drivers/GoogleDriver.ts +38 -0
  162. package/src/drivers/LdapDirectory.ts +185 -0
  163. package/src/drivers/LinkedInDriver.ts +105 -0
  164. package/src/drivers/LinkedInOpenidConnectDriver.ts +33 -0
  165. package/src/drivers/OidcDriver.ts +379 -0
  166. package/src/drivers/SamlDriver.ts +326 -0
  167. package/src/drivers/SpotifyDriver.ts +38 -0
  168. package/src/drivers/TwitterDriver.ts +55 -0
  169. package/src/drivers/TwitterXDriver.ts +53 -0
  170. package/src/dsig.ts +405 -0
  171. package/src/index.ts +85 -0
  172. package/src/jwt.ts +263 -0
  173. package/src/ldap.ts +374 -0
  174. package/src/nodeEnv.ts +31 -0
  175. package/src/oauth1.ts +144 -0
  176. package/src/oidc.ts +208 -0
  177. package/src/quasar.ts +64 -0
  178. package/src/replay.ts +136 -0
  179. package/src/saml.ts +309 -0
  180. package/src/services/main.ts +47 -0
  181. package/src/testing/FakeTransit.ts +233 -0
  182. package/src/types.ts +150 -0
  183. package/src/xml.ts +438 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 C9up
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # @c9up/transit
2
+
3
+ > Federated sign-in for the Ream framework — everything that lets a person
4
+ > prove who they are through an authority your application does not own.
5
+
6
+ Today: SAML 2.0, LDAP, generic OpenID Connect, OAuth1, OAuth2, and the
7
+ providers that speak them — written here, without a dependency, down to the XML
8
+ reader a signature is canonicalized over and the BER a directory is asked in.
9
+
10
+ Part of **[Ream](https://github.com/C9up/ream)**. Independent, publishable
11
+ package — it imports nothing from the rest of the framework.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @c9up/transit
17
+ ```
18
+
19
+ ```ts
20
+ // reamrc.ts
21
+ providers: [
22
+ () => import('@c9up/transit/provider'),
23
+ ]
24
+ ```
25
+
26
+ `ream add @c9up/transit` installs it, registers the provider and writes
27
+ `config/transit.ts`. The rest of this page assumes that has run.
28
+
29
+ ## Configuration
30
+
31
+ ```ts
32
+ // config/transit.ts
33
+ import { defineConfig, socials } from '@c9up/transit'
34
+
35
+ export default defineConfig({
36
+ google: socials.google({
37
+ clientId: env.get('GOOGLE_CLIENT_ID'),
38
+ clientSecret: env.get('GOOGLE_CLIENT_SECRET'),
39
+ callbackUrl: 'https://acme.test/auth/google/callback',
40
+ }),
41
+ })
42
+ ```
43
+
44
+ The keys are yours: two entries may reach the same provider with different
45
+ credentials, and `use(name)` asks for the key.
46
+
47
+ ## The round trip
48
+
49
+ ```ts
50
+ import transit from '@c9up/transit/services/main'
51
+
52
+ // Send the user off
53
+ const { url, state, secret } = await transit.begin('google')
54
+ ctx.session.put('transit_state', state)
55
+ ctx.session.put('transit_secret', secret)
56
+ return ctx.response.redirect(url)
57
+
58
+ // ... and receive them back
59
+ const { user, token } = await transit.callback(
60
+ 'google',
61
+ ctx.request.input('code'),
62
+ ctx.request.input('state'),
63
+ ctx.session.pull('transit_state'),
64
+ ctx.session.pull('transit_secret'),
65
+ )
66
+ ```
67
+
68
+ `begin()` works for every provider. `secret` is `undefined` for a plain OAuth2
69
+ provider, the PKCE verifier for one that mandates it, and the request-token
70
+ secret for OAuth1 — storing and returning it unconditionally is what lets one
71
+ controller serve all three.
72
+
73
+ ## Providers
74
+
75
+ `socials.apple`, `.discord`, `.facebook`, `.github`, `.google`, `.linkedin`,
76
+ `.linkedinOpenidConnect`, `.spotify`, `.twitter` (OAuth1), `.twitterX` (OAuth2).
77
+
78
+ Sign in with Apple takes a key rather than a secret, its callback arrives as a
79
+ POST, and it sends the user's name exactly once — read it with
80
+ `parseAppleUser(ctx.request.input('user'))` and store it.
81
+
82
+ ## LDAP and Active Directory
83
+
84
+ ```ts
85
+ export default defineConfig({
86
+ staff: ldap({
87
+ url: 'ldaps://directory.acme.test',
88
+ baseDn: 'dc=acme,dc=test',
89
+ loginAttribute: 'uid',
90
+ bindDn: 'cn=reader,dc=acme,dc=test',
91
+ bindPassword: env.get('LDAP_PASSWORD'),
92
+ }),
93
+ })
94
+
95
+ const user = await transit.authenticate('staff', username, password)
96
+ ```
97
+
98
+ An empty password is refused before a socket is opened: a simple bind with no
99
+ password is an anonymous bind, and the directory answers success. And there is
100
+ no filter string — a filter is a structure whose values are written as octets,
101
+ so a username cannot become syntax.
102
+
103
+ ## SAML 2.0
104
+
105
+ ```ts
106
+ export default defineConfig({
107
+ corp: saml({
108
+ entityId: 'https://acme.test/saml',
109
+ callbackUrl: 'https://acme.test/saml/acs',
110
+ issuer: 'https://idp.acme.test/metadata',
111
+ signOnUrl: 'https://idp.acme.test/sso',
112
+ certificates: [env.get('IDP_CERTIFICATE')],
113
+ replayStore: replayStores.redis({ connection: 'main' }),
114
+ }),
115
+ })
116
+ ```
117
+
118
+ The certificates come from the provider's metadata and nowhere else. The
119
+ element the signature covers is the element that is read — nothing is looked up
120
+ in the document afterwards, which is what closes XML Signature Wrapping. An
121
+ assertion is a bearer token, so its id is remembered until it expires.
122
+
123
+ That record has to be **shared by every replica**, because it is what stops the
124
+ same assertion being presented twice. The in-process default bounds replay
125
+ within one process and not beyond it, so in production the driver refuses to
126
+ choose it for you — say which store you want:
127
+
128
+ ```ts
129
+ replayStore: replayStores.redis({ connection: 'main' }) // shared across replicas
130
+ replayStore: replayStores.memory() // a single process, deliberately
131
+ ```
132
+
133
+ Outside production the in-process store remains the default: one dev process is
134
+ the case it is correct for.
135
+
136
+ ## OpenID Connect
137
+
138
+ One driver for every conforming provider — Keycloak, Auth0, Okta, Entra ID,
139
+ Authentik, Zitadel. Give it an issuer; the endpoints, keys and algorithms come
140
+ from what the provider publishes.
141
+
142
+ ```ts
143
+ export default defineConfig({
144
+ work: oidc({
145
+ issuer: 'https://id.acme.com',
146
+ clientId: env.get('OIDC_CLIENT_ID'),
147
+ clientSecret: env.get('OIDC_CLIENT_SECRET'),
148
+ callbackUrl: 'https://acme.test/auth/work/callback',
149
+ }),
150
+ })
151
+ ```
152
+
153
+ The `id_token` is a signed statement about who the user is, so it is verified
154
+ before anyone is signed in: signature against the published key, algorithm
155
+ taken from what the provider **declared** rather than from the token's own
156
+ header, and every claim that binds it to this exchange — `iss`, `aud`, `exp`,
157
+ and the `nonce`. Only asymmetric signatures are accepted.
158
+
159
+ ## Before linking an account by email
160
+
161
+ `user.emailVerificationState` is `verified`, `unverified` or `unsupported`.
162
+ Only the first may be trusted to match an existing account: `unverified` means
163
+ anyone able to type that address at the provider now holds it, and
164
+ `unsupported` means the provider says nothing either way.
165
+
166
+ ## Testing
167
+
168
+ ```ts
169
+ import { FakeTransit } from '@c9up/transit/testing'
170
+
171
+ const transit = new FakeTransit().willReturn('google', { email: 'ada@acme.test' })
172
+ container.singleton(TransitManager, () => transit)
173
+
174
+ transit.assertSignedIn('google')
175
+ ```
176
+
177
+ The double still enforces the state round trip, the value `begin()` asked you
178
+ to keep, and — for a directory — the refusal of an empty password. A fake that
179
+ let a forgetful controller pass would teach applications to ship one.
180
+
181
+ ## Entry points
182
+
183
+ - `@c9up/transit` — main API
184
+ - `@c9up/transit/config` — `defineConfig`, `socials`, `oidc`, `saml`, `ldap`
185
+ - `@c9up/transit/provider` — Ream IoC provider
186
+ - `@c9up/transit/services/main` — container service accessor
187
+ - `@c9up/transit/testing` — `FakeTransit`
188
+
189
+ ## License
190
+
191
+ MIT
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The OAuth 1.0a three-legged flow.
3
+ *
4
+ * A different protocol from OAuth2, not a variation on it. Two things follow
5
+ * from that, and both shape this class:
6
+ *
7
+ * - The redirect cannot be built offline. The application first asks the
8
+ * provider for a request token, and only then knows where to send the
9
+ * user — so `redirectUrl()` throws here and `begin()` is the way in.
10
+ * - There is no bearer token. Every call is SIGNED, request token included,
11
+ * which is what `oauth1.ts` exists for.
12
+ *
13
+ * The CSRF check is the same idea as OAuth2's `state` under another name: the
14
+ * provider hands back the request token it was given, and it has to match the
15
+ * one this application issued. So it maps onto `callback(code, state,
16
+ * expectedState, secret)` without inventing a second shape — the verifier is
17
+ * the code, the returned token is the state, and the request-token secret is
18
+ * the secret kept from `begin()`.
19
+ */
20
+ import type { OAuthConfig, OAuthToken, RedirectRequest, TransitDriver, TransitUser } from "./types.js";
21
+ export declare abstract class Oauth1Driver implements TransitDriver {
22
+ #private;
23
+ protected readonly config: OAuthConfig;
24
+ /** Named in errors, so a failure says which provider refused. */
25
+ protected abstract readonly provider: string;
26
+ /** Where a request token is obtained, before anything else can happen. */
27
+ protected abstract readonly requestTokenUrl: string;
28
+ /** Where the user approves. */
29
+ protected abstract readonly authorizeUrl: string;
30
+ /** Where the request token is traded for an access token. */
31
+ protected abstract readonly accessTokenUrl: string;
32
+ /** Where the profile is read. */
33
+ protected abstract readonly userInfoUrl: string;
34
+ constructor(config: OAuthConfig);
35
+ /** Extra query parameters this provider wants on the profile request. */
36
+ protected userInfoParams(): Record<string, string>;
37
+ /** Turn this provider's profile payload into the shape Warden hands back. */
38
+ protected abstract mapUser(raw: Record<string, unknown>): TransitUser;
39
+ /**
40
+ * OAuth1 has no offline redirect: the URL carries a request token that only
41
+ * the provider can issue. Saying so is more useful than returning a URL
42
+ * that would be refused.
43
+ */
44
+ redirectUrl(): string;
45
+ /**
46
+ * Ask for a request token, and answer with where to send the user plus the
47
+ * two values to keep: the token, which the callback has to match, and its
48
+ * secret, without which the exchange cannot be signed.
49
+ */
50
+ begin(): Promise<RedirectRequest>;
51
+ /**
52
+ * `code` is the `oauth_verifier`, `state` the `oauth_token` the provider
53
+ * sent back, `expectedState` the one issued at `begin()`, and `secret` its
54
+ * secret.
55
+ */
56
+ callback(code: string, state?: string, expectedState?: string, secret?: string): Promise<{
57
+ user: TransitUser;
58
+ token: OAuthToken;
59
+ }>;
60
+ /**
61
+ * Read the profile behind a token already held. Unlike OAuth2 this needs
62
+ * the secret too — an OAuth1 token alone signs nothing.
63
+ */
64
+ userFromToken(accessToken: string, tokenSecret?: string): Promise<TransitUser>;
65
+ /** Read the profile with a signed GET. */
66
+ protected fetchUser(accessToken: string, tokenSecret: string): Promise<TransitUser>;
67
+ }
68
+ //# sourceMappingURL=Oauth1Driver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Oauth1Driver.d.ts","sourceRoot":"","sources":["../src/Oauth1Driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,OAAO,KAAK,EACX,WAAW,EACX,UAAU,EACV,eAAe,EACf,aAAa,EACb,WAAW,EACX,MAAM,YAAY,CAAC;AAGpB,8BAAsB,YAAa,YAAW,aAAa;;IAY9C,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,WAAW;IAXlD,iEAAiE;IACjE,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC7C,0EAA0E;IAC1E,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACpD,+BAA+B;IAC/B,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IACjD,6DAA6D;IAC7D,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IACnD,iCAAiC;IACjC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,MAAM,EAAE,WAAW;IAElD,yEAAyE;IACzE,SAAS,CAAC,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAIlD,6EAA6E;IAC7E,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW;IAErE;;;;OAIG;IACH,WAAW,IAAI,MAAM;IAMrB;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,eAAe,CAAC;IA2BvC;;;;OAIG;IACG,QAAQ,CACb,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC;IAsBpD;;;OAGG;IACG,aAAa,CAClB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,CAAC;IASvB,0CAA0C;cAC1B,SAAS,CACxB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,CAAC;CA8DvB"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * The OAuth 1.0a three-legged flow.
3
+ *
4
+ * A different protocol from OAuth2, not a variation on it. Two things follow
5
+ * from that, and both shape this class:
6
+ *
7
+ * - The redirect cannot be built offline. The application first asks the
8
+ * provider for a request token, and only then knows where to send the
9
+ * user — so `redirectUrl()` throws here and `begin()` is the way in.
10
+ * - There is no bearer token. Every call is SIGNED, request token included,
11
+ * which is what `oauth1.ts` exists for.
12
+ *
13
+ * The CSRF check is the same idea as OAuth2's `state` under another name: the
14
+ * provider hands back the request token it was given, and it has to match the
15
+ * one this application issued. So it maps onto `callback(code, state,
16
+ * expectedState, secret)` without inventing a second shape — the verifier is
17
+ * the code, the returned token is the state, and the request-token secret is
18
+ * the secret kept from `begin()`.
19
+ */
20
+ import { authorizationHeader, baseOauthParams, nonce, parseFormBody, } from "./oauth1.js";
21
+ import { assertOAuthState } from "./types.js";
22
+ export class Oauth1Driver {
23
+ config;
24
+ constructor(config) {
25
+ this.config = config;
26
+ }
27
+ /** Extra query parameters this provider wants on the profile request. */
28
+ userInfoParams() {
29
+ return {};
30
+ }
31
+ /**
32
+ * OAuth1 has no offline redirect: the URL carries a request token that only
33
+ * the provider can issue. Saying so is more useful than returning a URL
34
+ * that would be refused.
35
+ */
36
+ redirectUrl() {
37
+ throw new Error(`[transit] ${this.provider} uses OAuth1, whose redirect needs a request token from the provider. Use begin() instead of redirect().`);
38
+ }
39
+ /**
40
+ * Ask for a request token, and answer with where to send the user plus the
41
+ * two values to keep: the token, which the callback has to match, and its
42
+ * secret, without which the exchange cannot be signed.
43
+ */
44
+ async begin() {
45
+ const oauth = this.#oauth();
46
+ oauth.push(["oauth_callback", this.config.callbackUrl]);
47
+ const body = await this.#post(this.requestTokenUrl, oauth);
48
+ if (body.oauth_callback_confirmed !== "true") {
49
+ // The provider rejects a callback URL it does not have registered,
50
+ // and says so here rather than at the end of the flow.
51
+ throw new Error(`${this.provider} did not confirm the callback URL — check that '${this.config.callbackUrl}' is registered on the application.`);
52
+ }
53
+ const token = body.oauth_token;
54
+ const secret = body.oauth_token_secret;
55
+ if (!token || !secret) {
56
+ throw new Error(`${this.provider} OAuth: no request token in the response`);
57
+ }
58
+ return {
59
+ url: `${this.authorizeUrl}?oauth_token=${encodeURIComponent(token)}`,
60
+ state: token,
61
+ secret,
62
+ };
63
+ }
64
+ /**
65
+ * `code` is the `oauth_verifier`, `state` the `oauth_token` the provider
66
+ * sent back, `expectedState` the one issued at `begin()`, and `secret` its
67
+ * secret.
68
+ */
69
+ async callback(code, state, expectedState, secret) {
70
+ assertOAuthState(state, expectedState);
71
+ if (!secret) {
72
+ throw new Error(`[transit] ${this.provider} needs the request-token secret from begin() — store it with the token and pass it back here.`);
73
+ }
74
+ const oauth = this.#oauth(expectedState);
75
+ oauth.push(["oauth_verifier", code]);
76
+ const body = await this.#post(this.accessTokenUrl, oauth, secret);
77
+ const accessToken = body.oauth_token;
78
+ const tokenSecret = body.oauth_token_secret;
79
+ if (!accessToken || !tokenSecret) {
80
+ throw new Error(`${this.provider} OAuth: no access token in response`);
81
+ }
82
+ const user = await this.fetchUser(accessToken, tokenSecret);
83
+ return { user, token: { accessToken, tokenSecret } };
84
+ }
85
+ /**
86
+ * Read the profile behind a token already held. Unlike OAuth2 this needs
87
+ * the secret too — an OAuth1 token alone signs nothing.
88
+ */
89
+ async userFromToken(accessToken, tokenSecret) {
90
+ if (!tokenSecret) {
91
+ throw new Error(`[transit] ${this.provider} needs the token secret as well: an OAuth1 access token cannot sign a request on its own.`);
92
+ }
93
+ return this.fetchUser(accessToken, tokenSecret);
94
+ }
95
+ /** Read the profile with a signed GET. */
96
+ async fetchUser(accessToken, tokenSecret) {
97
+ const params = this.userInfoParams();
98
+ const query = new URLSearchParams(params).toString();
99
+ const url = query ? `${this.userInfoUrl}?${query}` : this.userInfoUrl;
100
+ const response = await fetch(url, {
101
+ headers: {
102
+ Authorization: authorizationHeader({
103
+ method: "GET",
104
+ url,
105
+ oauth: this.#oauth(accessToken),
106
+ consumerSecret: this.config.clientSecret,
107
+ tokenSecret,
108
+ }),
109
+ Accept: "application/json",
110
+ },
111
+ });
112
+ if (!response.ok) {
113
+ throw new Error(`${this.provider} profile request failed (HTTP ${response.status})`);
114
+ }
115
+ return this.mapUser((await response.json()));
116
+ }
117
+ /** The `oauth_*` set for one request, stamped now. */
118
+ #oauth(token) {
119
+ return baseOauthParams({
120
+ consumerKey: this.config.clientId,
121
+ token,
122
+ timestamp: Math.floor(Date.now() / 1000),
123
+ nonce: nonce(),
124
+ });
125
+ }
126
+ /** A signed POST to one of the token endpoints, whose body is form-encoded. */
127
+ async #post(url, oauth, tokenSecret) {
128
+ const response = await fetch(url, {
129
+ method: "POST",
130
+ headers: {
131
+ Authorization: authorizationHeader({
132
+ method: "POST",
133
+ url,
134
+ oauth,
135
+ consumerSecret: this.config.clientSecret,
136
+ tokenSecret,
137
+ }),
138
+ "Content-Type": "application/x-www-form-urlencoded",
139
+ },
140
+ });
141
+ if (!response.ok) {
142
+ throw new Error(`${this.provider} OAuth token exchange failed (HTTP ${response.status})`);
143
+ }
144
+ // These endpoints answer `key=value&…`, not JSON.
145
+ return parseFormBody(await response.text());
146
+ }
147
+ }
148
+ //# sourceMappingURL=Oauth1Driver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Oauth1Driver.js","sourceRoot":"","sources":["../src/Oauth1Driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACN,mBAAmB,EACnB,eAAe,EACf,KAAK,EAEL,aAAa,GACb,MAAM,aAAa,CAAC;AAQrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,OAAgB,YAAY;IAYF;IAA/B,YAA+B,MAAmB;QAAnB,WAAM,GAAN,MAAM,CAAa;IAAG,CAAC;IAEtD,yEAAyE;IAC/D,cAAc;QACvB,OAAO,EAAE,CAAC;IACX,CAAC;IAKD;;;;OAIG;IACH,WAAW;QACV,MAAM,IAAI,KAAK,CACd,aAAa,IAAI,CAAC,QAAQ,0GAA0G,CACpI,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK;QACV,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,CAAC,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;QAExD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;QAC3D,IAAI,IAAI,CAAC,wBAAwB,KAAK,MAAM,EAAE,CAAC;YAC9C,mEAAmE;YACnE,uDAAuD;YACvD,MAAM,IAAI,KAAK,CACd,GAAG,IAAI,CAAC,QAAQ,mDAAmD,IAAI,CAAC,MAAM,CAAC,WAAW,qCAAqC,CAC/H,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC;QACvC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACd,GAAG,IAAI,CAAC,QAAQ,0CAA0C,CAC1D,CAAC;QACH,CAAC;QAED,OAAO;YACN,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,gBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE;YACpE,KAAK,EAAE,KAAK;YACZ,MAAM;SACN,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CACb,IAAY,EACZ,KAAc,EACd,aAAsB,EACtB,MAAe;QAEf,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACd,aAAa,IAAI,CAAC,QAAQ,+FAA+F,CACzH,CAAC;QACH,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QACzC,KAAK,CAAC,IAAI,CAAC,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAElE,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACrC,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC;QAC5C,IAAI,CAAC,WAAW,IAAI,CAAC,WAAW,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,QAAQ,qCAAqC,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QAC5D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,aAAa,CAClB,WAAmB,EACnB,WAAoB;QAEpB,IAAI,CAAC,WAAW,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACd,aAAa,IAAI,CAAC,QAAQ,2FAA2F,CACrH,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACjD,CAAC;IAED,0CAA0C;IAChC,KAAK,CAAC,SAAS,CACxB,WAAmB,EACnB,WAAmB;QAEnB,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC;QAEtE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YACjC,OAAO,EAAE;gBACR,aAAa,EAAE,mBAAmB,CAAC;oBAClC,MAAM,EAAE,KAAK;oBACb,GAAG;oBACH,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;oBAC/B,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;oBACxC,WAAW;iBACX,CAAC;gBACF,MAAM,EAAE,kBAAkB;aAC1B;SACD,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACd,GAAG,IAAI,CAAC,QAAQ,iCAAiC,QAAQ,CAAC,MAAM,GAAG,CACnE,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAC,CAAC;IACzE,CAAC;IAED,sDAAsD;IACtD,MAAM,CAAC,KAAc;QACpB,OAAO,eAAe,CAAC;YACtB,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YACjC,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;YACxC,KAAK,EAAE,KAAK,EAAE;SACd,CAAC,CAAC;IACJ,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,KAAK,CACV,GAAW,EACX,KAAc,EACd,WAAoB;QAEpB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YACjC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACR,aAAa,EAAE,mBAAmB,CAAC;oBAClC,MAAM,EAAE,MAAM;oBACd,GAAG;oBACH,KAAK;oBACL,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;oBACxC,WAAW;iBACX,CAAC;gBACF,cAAc,EAAE,mCAAmC;aACnD;SACD,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACd,GAAG,IAAI,CAAC,QAAQ,sCAAsC,QAAQ,CAAC,MAAM,GAAG,CACxE,CAAC;QACH,CAAC;QACD,kDAAkD;QAClD,OAAO,aAAa,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;CACD"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The half of an OAuth2 sign-in every provider shares.
3
+ *
4
+ * Each provider differs in three places — where you send the user, where the
5
+ * code is exchanged, and what the profile payload is called — and agrees on
6
+ * everything else. This holds the everything else, so a driver is the three
7
+ * URLs, the default scopes, and the mapping from that provider's payload to
8
+ * {@link TransitUser}.
9
+ *
10
+ * Nothing here is stateful: the `state` and, where a provider requires it, the
11
+ * PKCE verifier are passed in and handed back by the caller. Warden does not
12
+ * own your session, so it cannot store them for you — and that is also what
13
+ * lets one driver serve several concurrent sign-ins.
14
+ */
15
+ import type { OAuthConfig, OAuthToken, RedirectRequest, TransitDriver, TransitUser } from "./types.js";
16
+ /** A provider's token response, before it is narrowed. */
17
+ type TokenResponse = Record<string, unknown>;
18
+ /**
19
+ * Mint a PKCE code verifier.
20
+ *
21
+ * Store it next to the `state` — the same place, with the same lifetime — and
22
+ * hand it back to `callback()`. A provider that requires PKCE binds the
23
+ * authorization code to this value, so a code intercepted on its way back is
24
+ * useless to whoever intercepted it.
25
+ */
26
+ export declare function createCodeVerifier(): string;
27
+ export declare abstract class Oauth2Driver implements TransitDriver {
28
+ #private;
29
+ protected readonly config: OAuthConfig;
30
+ /** Named in errors, so a failure says which provider refused. */
31
+ protected abstract readonly provider: string;
32
+ /** Where the user is sent to approve. */
33
+ protected abstract readonly authorizeUrl: string;
34
+ /** Where the authorization code is exchanged for a token. */
35
+ protected abstract readonly accessTokenUrl: string;
36
+ /** Where the profile is read. */
37
+ protected abstract readonly userInfoUrl: string;
38
+ /** Scopes requested when the config names none. */
39
+ protected abstract readonly defaultScopes: readonly string[];
40
+ /**
41
+ * Providers that require PKCE. The redirect then refuses to build without
42
+ * a verifier rather than sending the user to a URL the provider rejects.
43
+ */
44
+ protected readonly requiresPkce: boolean;
45
+ /**
46
+ * Send the client credentials as HTTP Basic on the token request instead of
47
+ * in the body. A handful of providers accept nothing else.
48
+ */
49
+ protected readonly tokenAuth: "body" | "basic";
50
+ constructor(config: OAuthConfig);
51
+ /** Extra parameters this provider wants on the authorize URL. */
52
+ protected authorizeParams(): Record<string, string>;
53
+ /** Extra query parameters this provider wants on the profile request. */
54
+ protected userInfoParams(): Record<string, string>;
55
+ /** Turn this provider's profile payload into the shape Warden hands back. */
56
+ protected abstract mapUser(raw: Record<string, unknown>): TransitUser;
57
+ scopes(): string[];
58
+ redirectUrl(state?: string, secret?: string): string;
59
+ /**
60
+ * Where to send the user, plus what has to be kept until they come back.
61
+ * The state is minted here when the caller has no reason to choose one,
62
+ * and a provider requiring PKCE gets its verifier without the caller
63
+ * having to know it needed one.
64
+ */
65
+ begin(state?: string): Promise<RedirectRequest>;
66
+ callback(code: string, state?: string, expectedState?: string, secret?: string): Promise<{
67
+ user: TransitUser;
68
+ token: OAuthToken;
69
+ }>;
70
+ /**
71
+ * Read the profile behind a token you already hold, with no code to
72
+ * exchange — a refreshed token, or one a mobile client obtained itself.
73
+ */
74
+ userFromToken(accessToken: string): Promise<TransitUser>;
75
+ /** Trade the authorization code for an access token. */
76
+ protected exchange(code: string, secret?: string): Promise<TokenResponse>;
77
+ /** Read the profile. Providers needing a second call override this. */
78
+ protected fetchUser(accessToken: string): Promise<TransitUser>;
79
+ /** An authenticated GET returning a JSON array — some endpoints answer one. */
80
+ protected getList(url: string, accessToken: string, params?: Record<string, string>): Promise<unknown[]>;
81
+ /** An authenticated GET returning the decoded JSON body. */
82
+ protected get(url: string, accessToken: string, params?: Record<string, string>): Promise<Record<string, unknown>>;
83
+ }
84
+ /** The S256 challenge a provider compares the verifier against. */
85
+ export declare function challengeFor(secret: string): string;
86
+ /** Read a string field, or `undefined` when it is absent or not a string. */
87
+ export declare function str(raw: Record<string, unknown>, key: string): string | undefined;
88
+ export {};
89
+ //# sourceMappingURL=Oauth2Driver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Oauth2Driver.d.ts","sourceRoot":"","sources":["../src/Oauth2Driver.ts"],"names":[],"mappings":"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"}