@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.
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/Oauth1Driver.d.ts +68 -0
- package/dist/Oauth1Driver.d.ts.map +1 -0
- package/dist/Oauth1Driver.js +148 -0
- package/dist/Oauth1Driver.js.map +1 -0
- package/dist/Oauth2Driver.d.ts +89 -0
- package/dist/Oauth2Driver.d.ts.map +1 -0
- package/dist/Oauth2Driver.js +196 -0
- package/dist/Oauth2Driver.js.map +1 -0
- package/dist/TransitManager.d.ts +59 -0
- package/dist/TransitManager.d.ts.map +1 -0
- package/dist/TransitManager.js +100 -0
- package/dist/TransitManager.js.map +1 -0
- package/dist/TransitProvider.d.ts +26 -0
- package/dist/TransitProvider.d.ts.map +1 -0
- package/dist/TransitProvider.js +32 -0
- package/dist/TransitProvider.js.map +1 -0
- package/dist/apple.d.ts +45 -0
- package/dist/apple.d.ts.map +1 -0
- package/dist/apple.js +80 -0
- package/dist/apple.js.map +1 -0
- package/dist/ber.d.ts +50 -0
- package/dist/ber.d.ts.map +1 -0
- package/dist/ber.js +156 -0
- package/dist/ber.js.map +1 -0
- package/dist/c14n.d.ts +38 -0
- package/dist/c14n.d.ts.map +1 -0
- package/dist/c14n.js +129 -0
- package/dist/c14n.js.map +1 -0
- package/dist/config.d.ts +115 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +146 -0
- package/dist/config.js.map +1 -0
- package/dist/configure.d.ts +18 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +33 -0
- package/dist/configure.js.map +1 -0
- package/dist/drivers/AppleDriver.d.ts +36 -0
- package/dist/drivers/AppleDriver.d.ts.map +1 -0
- package/dist/drivers/AppleDriver.js +58 -0
- package/dist/drivers/AppleDriver.js.map +1 -0
- package/dist/drivers/DiscordDriver.d.ts +15 -0
- package/dist/drivers/DiscordDriver.d.ts.map +1 -0
- package/dist/drivers/DiscordDriver.js +45 -0
- package/dist/drivers/DiscordDriver.js.map +1 -0
- package/dist/drivers/FacebookDriver.d.ts +15 -0
- package/dist/drivers/FacebookDriver.d.ts.map +1 -0
- package/dist/drivers/FacebookDriver.js +47 -0
- package/dist/drivers/FacebookDriver.js.map +1 -0
- package/dist/drivers/GitHubDriver.d.ts +33 -0
- package/dist/drivers/GitHubDriver.d.ts.map +1 -0
- package/dist/drivers/GitHubDriver.js +72 -0
- package/dist/drivers/GitHubDriver.js.map +1 -0
- package/dist/drivers/GoogleDriver.d.ts +16 -0
- package/dist/drivers/GoogleDriver.d.ts.map +1 -0
- package/dist/drivers/GoogleDriver.js +31 -0
- package/dist/drivers/GoogleDriver.js.map +1 -0
- package/dist/drivers/LdapDirectory.d.ts +51 -0
- package/dist/drivers/LdapDirectory.d.ts.map +1 -0
- package/dist/drivers/LdapDirectory.js +127 -0
- package/dist/drivers/LdapDirectory.js.map +1 -0
- package/dist/drivers/LinkedInDriver.d.ts +29 -0
- package/dist/drivers/LinkedInDriver.d.ts.map +1 -0
- package/dist/drivers/LinkedInDriver.js +97 -0
- package/dist/drivers/LinkedInDriver.js.map +1 -0
- package/dist/drivers/LinkedInOpenidConnectDriver.d.ts +17 -0
- package/dist/drivers/LinkedInOpenidConnectDriver.d.ts.map +1 -0
- package/dist/drivers/LinkedInOpenidConnectDriver.js +27 -0
- package/dist/drivers/LinkedInOpenidConnectDriver.js.map +1 -0
- package/dist/drivers/OidcDriver.d.ts +64 -0
- package/dist/drivers/OidcDriver.d.ts.map +1 -0
- package/dist/drivers/OidcDriver.js +259 -0
- package/dist/drivers/OidcDriver.js.map +1 -0
- package/dist/drivers/SamlDriver.d.ts +66 -0
- package/dist/drivers/SamlDriver.d.ts.map +1 -0
- package/dist/drivers/SamlDriver.js +228 -0
- package/dist/drivers/SamlDriver.js.map +1 -0
- package/dist/drivers/SpotifyDriver.d.ts +16 -0
- package/dist/drivers/SpotifyDriver.d.ts.map +1 -0
- package/dist/drivers/SpotifyDriver.js +34 -0
- package/dist/drivers/SpotifyDriver.js.map +1 -0
- package/dist/drivers/TwitterDriver.d.ts +21 -0
- package/dist/drivers/TwitterDriver.d.ts.map +1 -0
- package/dist/drivers/TwitterDriver.js +46 -0
- package/dist/drivers/TwitterDriver.js.map +1 -0
- package/dist/drivers/TwitterXDriver.d.ts +22 -0
- package/dist/drivers/TwitterXDriver.d.ts.map +1 -0
- package/dist/drivers/TwitterXDriver.js +47 -0
- package/dist/drivers/TwitterXDriver.js.map +1 -0
- package/dist/dsig.d.ts +51 -0
- package/dist/dsig.d.ts.map +1 -0
- package/dist/dsig.js +273 -0
- package/dist/dsig.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt.d.ts +135 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +167 -0
- package/dist/jwt.js.map +1 -0
- package/dist/ldap.d.ts +82 -0
- package/dist/ldap.d.ts.map +1 -0
- package/dist/ldap.js +271 -0
- package/dist/ldap.js.map +1 -0
- package/dist/nodeEnv.d.ts +17 -0
- package/dist/nodeEnv.d.ts.map +1 -0
- package/dist/nodeEnv.js +33 -0
- package/dist/nodeEnv.js.map +1 -0
- package/dist/oauth1.d.ts +68 -0
- package/dist/oauth1.d.ts.map +1 -0
- package/dist/oauth1.js +106 -0
- package/dist/oauth1.js.map +1 -0
- package/dist/oidc.d.ts +61 -0
- package/dist/oidc.d.ts.map +1 -0
- package/dist/oidc.js +151 -0
- package/dist/oidc.js.map +1 -0
- package/dist/quasar.d.ts +14 -0
- package/dist/quasar.d.ts.map +1 -0
- package/dist/quasar.js +42 -0
- package/dist/quasar.js.map +1 -0
- package/dist/replay.d.ts +70 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +82 -0
- package/dist/replay.js.map +1 -0
- package/dist/saml.d.ts +68 -0
- package/dist/saml.d.ts.map +1 -0
- package/dist/saml.js +181 -0
- package/dist/saml.js.map +1 -0
- package/dist/services/main.d.ts +22 -0
- package/dist/services/main.d.ts.map +1 -0
- package/dist/services/main.js +39 -0
- package/dist/services/main.js.map +1 -0
- package/dist/testing/FakeTransit.d.ts +82 -0
- package/dist/testing/FakeTransit.d.ts.map +1 -0
- package/dist/testing/FakeTransit.js +160 -0
- package/dist/testing/FakeTransit.js.map +1 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +27 -0
- package/dist/types.js.map +1 -0
- package/dist/xml.d.ts +72 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +353 -0
- package/dist/xml.js.map +1 -0
- package/package.json +73 -0
- package/src/Oauth1Driver.ts +216 -0
- package/src/Oauth2Driver.ts +271 -0
- package/src/TransitManager.ts +145 -0
- package/src/TransitProvider.ts +51 -0
- package/src/apple.ts +101 -0
- package/src/ber.ts +175 -0
- package/src/c14n.ts +187 -0
- package/src/config.ts +160 -0
- package/src/configure.ts +47 -0
- package/src/drivers/AppleDriver.ts +69 -0
- package/src/drivers/DiscordDriver.ts +50 -0
- package/src/drivers/FacebookDriver.ts +53 -0
- package/src/drivers/GitHubDriver.ts +91 -0
- package/src/drivers/GoogleDriver.ts +38 -0
- package/src/drivers/LdapDirectory.ts +185 -0
- package/src/drivers/LinkedInDriver.ts +105 -0
- package/src/drivers/LinkedInOpenidConnectDriver.ts +33 -0
- package/src/drivers/OidcDriver.ts +379 -0
- package/src/drivers/SamlDriver.ts +326 -0
- package/src/drivers/SpotifyDriver.ts +38 -0
- package/src/drivers/TwitterDriver.ts +55 -0
- package/src/drivers/TwitterXDriver.ts +53 -0
- package/src/dsig.ts +405 -0
- package/src/index.ts +85 -0
- package/src/jwt.ts +263 -0
- package/src/ldap.ts +374 -0
- package/src/nodeEnv.ts +31 -0
- package/src/oauth1.ts +144 -0
- package/src/oidc.ts +208 -0
- package/src/quasar.ts +64 -0
- package/src/replay.ts +136 -0
- package/src/saml.ts +309 -0
- package/src/services/main.ts +47 -0
- package/src/testing/FakeTransit.ts +233 -0
- package/src/types.ts +150 -0
- 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":"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"}
|
|
@@ -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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"}
|