@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
@@ -0,0 +1,228 @@
1
+ /**
2
+ * SAML 2.0 Web Browser SSO.
3
+ *
4
+ * The pieces are the four layers below this one; what this adds is the walk
5
+ * between them, and the two bindings a browser sign-in uses: the request
6
+ * leaves deflated in a query string, and the response comes back base64 in a
7
+ * form POST.
8
+ *
9
+ * The order here is the security. The signature is checked FIRST, and
10
+ * everything read afterwards comes out of the element it covers — the status
11
+ * being the one exception, and a deliberate one.
12
+ */
13
+ import { randomUUID } from "node:crypto";
14
+ import { deflateRawSync } from "node:zlib";
15
+ import { verifyXmlSignature } from "../dsig.js";
16
+ import { inProduction } from "../nodeEnv.js";
17
+ import { MemoryAssertionReplayStore, } from "../replay.js";
18
+ import { assertionInside, assertResponseSucceeded, SamlError, validateAssertion, } from "../saml.js";
19
+ import { assertOAuthState } from "../types.js";
20
+ import { parseXml } from "../xml.js";
21
+ const PROTOCOL_NS = "urn:oasis:names:tc:SAML:2.0:protocol";
22
+ const ASSERTION_NS = "urn:oasis:names:tc:SAML:2.0:assertion";
23
+ const HTTP_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
24
+ const EMAIL_FORMAT = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
25
+ /**
26
+ * The attribute names providers actually use for the same three things. A
27
+ * config names its own when its provider invents another.
28
+ */
29
+ const EMAIL_CLAIMS = [
30
+ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
31
+ "urn:oid:0.9.2342.19200300.100.1.3",
32
+ "email",
33
+ "mail",
34
+ "emailAddress",
35
+ ];
36
+ const NAME_CLAIMS = [
37
+ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
38
+ "urn:oid:2.16.840.1.113730.3.1.241",
39
+ "displayName",
40
+ "cn",
41
+ "name",
42
+ ];
43
+ const NICK_CLAIMS = [
44
+ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
45
+ "urn:oid:0.9.2342.19200300.100.1.1",
46
+ "uid",
47
+ "givenName",
48
+ ];
49
+ /**
50
+ * Refuse to fall into the memory replay store by DEFAULT in production.
51
+ *
52
+ * A store that bounds replay per process is real protection on one replica and
53
+ * none at all on two, and nothing here can tell which is deployed. Defaulting
54
+ * quietly means the protection disappears exactly when a service is scaled —
55
+ * the moment nobody is looking at this file.
56
+ *
57
+ * Outside production the default stands: a single dev process is the case it
58
+ * is correct for.
59
+ */
60
+ function refuseImplicitMemoryReplayStoreInProduction() {
61
+ if (!inProduction())
62
+ return;
63
+ throw new SamlError("SAML replay protection has no store configured, and the in-process default only bounds replay within ONE process — behind a second replica the same assertion is accepted again on each.\n" +
64
+ " Set `replayStore: replayStores.redis({ connection })` to share the record across replicas,\n" +
65
+ " or `replayStores.memory()` to state that a single process is intended.");
66
+ }
67
+ export class SamlDriver {
68
+ #config;
69
+ #replay;
70
+ constructor(config) {
71
+ if (config.certificates.length === 0) {
72
+ // Without a certificate there is nothing to verify against, and a
73
+ // driver that discovered that at the first sign-in would have let the
74
+ // application boot looking healthy.
75
+ throw new SamlError(`no signing certificate for '${config.issuer}' — take them from the provider's metadata`);
76
+ }
77
+ if (config.replayStore === undefined) {
78
+ // Decided here, not on the first sign-in: a driver that discovered
79
+ // this mid-request would have let the application boot looking
80
+ // healthy, and reported it to whoever happened to sign in first.
81
+ refuseImplicitMemoryReplayStoreInProduction();
82
+ }
83
+ this.#config = config;
84
+ }
85
+ /**
86
+ * A SAML request carries its own id, and that id is what the response has
87
+ * to answer. There is nothing to build without minting one.
88
+ */
89
+ redirectUrl() {
90
+ throw new Error("[transit] a SAML sign-in carries a request id the response must answer. Use begin() instead of redirect().");
91
+ }
92
+ async begin(state = randomUUID()) {
93
+ // A SAML id is an XML name: it may not start with a digit, and a bare
94
+ // UUID often does.
95
+ const requestId = `_${randomUUID()}`;
96
+ const request = this.#authnRequest(requestId);
97
+ // The redirect binding carries the request deflated — raw, with no zlib
98
+ // header, which is what the specification asks for.
99
+ const deflated = deflateRawSync(Buffer.from(request, "utf8")).toString("base64");
100
+ const params = new URLSearchParams({
101
+ SAMLRequest: deflated,
102
+ RelayState: state,
103
+ });
104
+ return {
105
+ url: `${this.#config.signOnUrl}${this.#config.signOnUrl.includes("?") ? "&" : "?"}${params}`,
106
+ state,
107
+ // The response is checked against this id; storing it is not optional.
108
+ secret: requestId,
109
+ };
110
+ }
111
+ /**
112
+ * `code` is the base64 `SAMLResponse` from the form post, `state` the
113
+ * `RelayState` that came back, `expectedState` the one sent, and `secret`
114
+ * the request id `begin()` minted.
115
+ */
116
+ async callback(code, state, expectedState, secret) {
117
+ assertOAuthState(state, expectedState);
118
+ if (!secret) {
119
+ throw new SamlError("the request id from begin() is missing — store it with the state and pass it back, or any assertion can be posted here");
120
+ }
121
+ const document = parseXml(decode(code));
122
+ if (document.namespaceUri !== PROTOCOL_NS ||
123
+ document.local !== "Response") {
124
+ throw new SamlError(`expected a <Response>, and the document is <${document.qname}>`);
125
+ }
126
+ // Read from the unsigned document exactly once, and only this: a refusal
127
+ // arrives unsigned when the assertion is what carries the signature, and
128
+ // it exists to be reported rather than trusted. Flipping it the other
129
+ // way cannot produce an assertion that verifies.
130
+ assertResponseSucceeded(document);
131
+ // From here on, only what the signature covers.
132
+ const signed = verifyXmlSignature(document, {
133
+ certificates: this.#config.certificates,
134
+ });
135
+ const assertion = assertionInside(signed);
136
+ const identity = validateAssertion(assertion, {
137
+ issuer: this.#config.issuer,
138
+ audience: this.#config.entityId,
139
+ recipient: this.#config.callbackUrl,
140
+ inResponseTo: secret,
141
+ clockSkewSeconds: this.#config.clockSkewSeconds,
142
+ });
143
+ const fresh = await this.#replayStore().remember(identity.assertionId, identity.notOnOrAfter);
144
+ if (!fresh) {
145
+ // Everything above passes on a captured response posted a second
146
+ // time. This is what does not.
147
+ throw new SamlError("this assertion has already been used — a response cannot sign somebody in twice");
148
+ }
149
+ return {
150
+ user: this.#mapUser(identity),
151
+ // SAML hands over a statement, not a token to go asking with. The
152
+ // session index is what a single logout would name.
153
+ token: { accessToken: identity.sessionIndex ?? identity.assertionId },
154
+ };
155
+ }
156
+ #replayStore() {
157
+ if (!this.#replay) {
158
+ const configured = this.#config.replayStore;
159
+ this.#replay =
160
+ typeof configured === "function"
161
+ ? configured()
162
+ : (configured ?? new MemoryAssertionReplayStore());
163
+ }
164
+ return this.#replay;
165
+ }
166
+ #authnRequest(id) {
167
+ const format = this.#config.nameIdFormat;
168
+ return (`<samlp:AuthnRequest xmlns:samlp="${PROTOCOL_NS}"` +
169
+ ` ID="${escapeXml(id)}" Version="2.0"` +
170
+ ` IssueInstant="${new Date().toISOString()}"` +
171
+ ` Destination="${escapeXml(this.#config.signOnUrl)}"` +
172
+ ` AssertionConsumerServiceURL="${escapeXml(this.#config.callbackUrl)}"` +
173
+ ` ProtocolBinding="${HTTP_POST}">` +
174
+ `<saml:Issuer xmlns:saml="${ASSERTION_NS}">${escapeXml(this.#config.entityId)}</saml:Issuer>` +
175
+ (format
176
+ ? `<samlp:NameIDPolicy Format="${escapeXml(format)}" AllowCreate="true"/>`
177
+ : "") +
178
+ "</samlp:AuthnRequest>");
179
+ }
180
+ #mapUser(identity) {
181
+ const claims = this.#config.claims ?? {};
182
+ const email = pick(identity, claims.email, EMAIL_CLAIMS) ??
183
+ (identity.nameIdFormat === EMAIL_FORMAT ? identity.nameId : undefined);
184
+ const name = pick(identity, claims.name, NAME_CLAIMS);
185
+ return {
186
+ // The NameID is what the provider calls this person, and the only
187
+ // value guaranteed to be there.
188
+ id: identity.nameId,
189
+ email: email ?? "",
190
+ name: name ?? identity.nameId,
191
+ nickName: pick(identity, claims.nickName, NICK_CLAIMS),
192
+ // A provider that asserts an address has vouched for it: that is what
193
+ // its directory is.
194
+ emailVerificationState: email === undefined ? "unsupported" : "verified",
195
+ raw: {
196
+ nameId: identity.nameId,
197
+ nameIdFormat: identity.nameIdFormat,
198
+ sessionIndex: identity.sessionIndex,
199
+ attributes: identity.attributes,
200
+ },
201
+ };
202
+ }
203
+ }
204
+ /** The first value among the configured name, then the usual ones. */
205
+ function pick(identity, configured, candidates) {
206
+ const names = configured ? [configured] : candidates;
207
+ for (const name of names) {
208
+ const value = identity.attributes[name]?.[0];
209
+ if (value !== undefined && value !== "")
210
+ return value;
211
+ }
212
+ return undefined;
213
+ }
214
+ function decode(value) {
215
+ const text = Buffer.from(value.replace(/\s+/g, ""), "base64").toString("utf8");
216
+ if (!text.includes("<")) {
217
+ throw new SamlError("the SAMLResponse is not base64-encoded XML");
218
+ }
219
+ return text;
220
+ }
221
+ function escapeXml(value) {
222
+ return value
223
+ .replace(/&/g, "&amp;")
224
+ .replace(/</g, "&lt;")
225
+ .replace(/>/g, "&gt;")
226
+ .replace(/"/g, "&quot;");
227
+ }
228
+ //# sourceMappingURL=SamlDriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SamlDriver.js","sourceRoot":"","sources":["../../src/drivers/SamlDriver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Spotify OAuth2 driver.
3
+ */
4
+ import { Oauth2Driver } from "../Oauth2Driver.js";
5
+ import type { TransitUser } from "../types.js";
6
+ export declare class SpotifyDriver extends Oauth2Driver {
7
+ protected readonly provider = "Spotify";
8
+ protected readonly authorizeUrl = "https://accounts.spotify.com/authorize";
9
+ protected readonly accessTokenUrl = "https://accounts.spotify.com/api/token";
10
+ protected readonly userInfoUrl = "https://api.spotify.com/v1/me";
11
+ protected readonly defaultScopes: readonly ["user-read-email"];
12
+ /** Spotify wants the client credentials as HTTP Basic on the token request. */
13
+ protected readonly tokenAuth: "basic";
14
+ protected mapUser(raw: Record<string, unknown>): TransitUser;
15
+ }
16
+ //# sourceMappingURL=SpotifyDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpotifyDriver.d.ts","sourceRoot":"","sources":["../../src/drivers/SpotifyDriver.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,qBAAa,aAAc,SAAQ,YAAY;IAC9C,SAAS,CAAC,QAAQ,CAAC,QAAQ,aAAa;IACxC,SAAS,CAAC,QAAQ,CAAC,YAAY,4CAA4C;IAC3E,SAAS,CAAC,QAAQ,CAAC,cAAc,4CAA4C;IAC7E,SAAS,CAAC,QAAQ,CAAC,WAAW,mCAAmC;IACjE,SAAS,CAAC,QAAQ,CAAC,aAAa,+BAAgC;IAChE,+EAA+E;IAC/E,mBAA4B,SAAS,EAAG,OAAO,CAAU;IAEzD,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW;CAa5D"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Spotify OAuth2 driver.
3
+ */
4
+ import { Oauth2Driver } from "../Oauth2Driver.js";
5
+ export class SpotifyDriver extends Oauth2Driver {
6
+ provider = "Spotify";
7
+ authorizeUrl = "https://accounts.spotify.com/authorize";
8
+ accessTokenUrl = "https://accounts.spotify.com/api/token";
9
+ userInfoUrl = "https://api.spotify.com/v1/me";
10
+ defaultScopes = ["user-read-email"];
11
+ /** Spotify wants the client credentials as HTTP Basic on the token request. */
12
+ tokenAuth = "basic";
13
+ mapUser(raw) {
14
+ return {
15
+ id: String(raw.id ?? ""),
16
+ email: String(raw.email ?? ""),
17
+ name: String(raw.display_name ?? ""),
18
+ nickName: typeof raw.display_name === "string" ? raw.display_name : undefined,
19
+ avatarUrl: firstImage(raw),
20
+ // Spotify says nothing about the address it returns.
21
+ emailVerificationState: "unsupported",
22
+ raw,
23
+ };
24
+ }
25
+ }
26
+ /** Spotify returns a list of images; the first is the one to show. */
27
+ function firstImage(raw) {
28
+ const images = raw.images;
29
+ if (!Array.isArray(images) || images.length === 0)
30
+ return undefined;
31
+ const url = Reflect.get(Object(images[0]), "url");
32
+ return typeof url === "string" && url !== "" ? url : undefined;
33
+ }
34
+ //# sourceMappingURL=SpotifyDriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpotifyDriver.js","sourceRoot":"","sources":["../../src/drivers/SpotifyDriver.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,OAAO,aAAc,SAAQ,YAAY;IAC3B,QAAQ,GAAG,SAAS,CAAC;IACrB,YAAY,GAAG,wCAAwC,CAAC;IACxD,cAAc,GAAG,wCAAwC,CAAC;IAC1D,WAAW,GAAG,+BAA+B,CAAC;IAC9C,aAAa,GAAG,CAAC,iBAAiB,CAAU,CAAC;IAChE,+EAA+E;IACnD,SAAS,GAAG,OAAgB,CAAC;IAE/C,OAAO,CAAC,GAA4B;QAC7C,OAAO;YACN,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;YACxB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAC9B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;YACpC,QAAQ,EACP,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;YACpE,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC;YAC1B,qDAAqD;YACrD,sBAAsB,EAAE,aAAa;YACrC,GAAG;SACH,CAAC;IACH,CAAC;CACD;AAED,sEAAsE;AACtE,SAAS,UAAU,CAAC,GAA4B;IAC/C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACpE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAClD,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAChE,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * X (Twitter) OAuth1 driver.
3
+ *
4
+ * The older of X's two flows. `TwitterXDriver` is the OAuth2 one, and is what
5
+ * a new application wants; this one exists for an application already built
6
+ * on OAuth1 tokens, and it is also the only flow that returns the address
7
+ * through `verify_credentials`.
8
+ */
9
+ import { Oauth1Driver } from "../Oauth1Driver.js";
10
+ import type { TransitUser } from "../types.js";
11
+ export declare class TwitterDriver extends Oauth1Driver {
12
+ protected readonly provider = "X";
13
+ protected readonly requestTokenUrl = "https://api.twitter.com/oauth/request_token";
14
+ /** `authenticate`, not `authorize`: a returning user is not asked twice. */
15
+ protected readonly authorizeUrl = "https://api.twitter.com/oauth/authenticate";
16
+ protected readonly accessTokenUrl = "https://api.twitter.com/oauth/access_token";
17
+ protected readonly userInfoUrl = "https://api.twitter.com/1.1/account/verify_credentials.json";
18
+ protected userInfoParams(): Record<string, string>;
19
+ protected mapUser(raw: Record<string, unknown>): TransitUser;
20
+ }
21
+ //# sourceMappingURL=TwitterDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TwitterDriver.d.ts","sourceRoot":"","sources":["../../src/drivers/TwitterDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,qBAAa,aAAc,SAAQ,YAAY;IAC9C,SAAS,CAAC,QAAQ,CAAC,QAAQ,OAAO;IAClC,SAAS,CAAC,QAAQ,CAAC,eAAe,iDACa;IAC/C,4EAA4E;IAC5E,SAAS,CAAC,QAAQ,CAAC,YAAY,gDACe;IAC9C,SAAS,CAAC,QAAQ,CAAC,cAAc,gDACa;IAC9C,SAAS,CAAC,QAAQ,CAAC,WAAW,iEACiC;cAE5C,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAM3D,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW;CAc5D"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * X (Twitter) OAuth1 driver.
3
+ *
4
+ * The older of X's two flows. `TwitterXDriver` is the OAuth2 one, and is what
5
+ * a new application wants; this one exists for an application already built
6
+ * on OAuth1 tokens, and it is also the only flow that returns the address
7
+ * through `verify_credentials`.
8
+ */
9
+ import { Oauth1Driver } from "../Oauth1Driver.js";
10
+ export class TwitterDriver extends Oauth1Driver {
11
+ provider = "X";
12
+ requestTokenUrl = "https://api.twitter.com/oauth/request_token";
13
+ /** `authenticate`, not `authorize`: a returning user is not asked twice. */
14
+ authorizeUrl = "https://api.twitter.com/oauth/authenticate";
15
+ accessTokenUrl = "https://api.twitter.com/oauth/access_token";
16
+ userInfoUrl = "https://api.twitter.com/1.1/account/verify_credentials.json";
17
+ userInfoParams() {
18
+ // The address is omitted unless asked for, and only reaches an
19
+ // application whose X app was granted it.
20
+ return { include_email: "true" };
21
+ }
22
+ mapUser(raw) {
23
+ return {
24
+ // `id_str`, never `id`: the numeric form loses precision past 2^53.
25
+ id: String(raw.id_str ?? ""),
26
+ email: String(raw.email ?? ""),
27
+ name: String(raw.name ?? raw.screen_name ?? ""),
28
+ nickName: typeof raw.screen_name === "string" ? raw.screen_name : undefined,
29
+ avatarUrl: fullSizeAvatar(raw),
30
+ // X vouches for nothing about the address it returns.
31
+ emailVerificationState: "unsupported",
32
+ raw,
33
+ };
34
+ }
35
+ }
36
+ /**
37
+ * The profile carries the 48px thumbnail. The full-size original is the same
38
+ * URL with the `_normal` suffix removed.
39
+ */
40
+ function fullSizeAvatar(raw) {
41
+ const url = raw.profile_image_url_https;
42
+ if (typeof url !== "string" || url === "")
43
+ return undefined;
44
+ return url.replace(/_normal(\.[a-z]+)$/i, "$1");
45
+ }
46
+ //# sourceMappingURL=TwitterDriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TwitterDriver.js","sourceRoot":"","sources":["../../src/drivers/TwitterDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,OAAO,aAAc,SAAQ,YAAY;IAC3B,QAAQ,GAAG,GAAG,CAAC;IACf,eAAe,GACjC,6CAA6C,CAAC;IAC/C,4EAA4E;IACzD,YAAY,GAC9B,4CAA4C,CAAC;IAC3B,cAAc,GAChC,4CAA4C,CAAC;IAC3B,WAAW,GAC7B,6DAA6D,CAAC;IAE5C,cAAc;QAChC,+DAA+D;QAC/D,0CAA0C;QAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAClC,CAAC;IAES,OAAO,CAAC,GAA4B;QAC7C,OAAO;YACN,oEAAoE;YACpE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;YAC5B,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAC9B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;YAC/C,QAAQ,EACP,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;YAClE,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC;YAC9B,sDAAsD;YACtD,sBAAsB,EAAE,aAAa;YACrC,GAAG;SACH,CAAC;IACH,CAAC;CACD;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,GAA4B;IACnD,MAAM,GAAG,GAAG,GAAG,CAAC,uBAAuB,CAAC;IACxC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC5D,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * X (Twitter) OAuth2 driver.
3
+ *
4
+ * X requires PKCE, so this driver refuses to build a redirect without a code
5
+ * verifier. Mint one with `createCodeVerifier()`, store it beside the state,
6
+ * and hand it back to `callback()`.
7
+ */
8
+ import { Oauth2Driver } from "../Oauth2Driver.js";
9
+ import type { TransitUser } from "../types.js";
10
+ export declare class TwitterXDriver extends Oauth2Driver {
11
+ protected readonly provider = "X";
12
+ protected readonly authorizeUrl = "https://x.com/i/oauth2/authorize";
13
+ protected readonly accessTokenUrl = "https://api.x.com/2/oauth2/token";
14
+ protected readonly userInfoUrl = "https://api.x.com/2/users/me";
15
+ protected readonly defaultScopes: readonly ["tweet.read", "users.read", "users.email"];
16
+ protected readonly requiresPkce = true;
17
+ /** X accepts the client credentials only as HTTP Basic. */
18
+ protected readonly tokenAuth: "basic";
19
+ protected userInfoParams(): Record<string, string>;
20
+ protected mapUser(raw: Record<string, unknown>): TransitUser;
21
+ }
22
+ //# sourceMappingURL=TwitterXDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TwitterXDriver.d.ts","sourceRoot":"","sources":["../../src/drivers/TwitterXDriver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,qBAAa,cAAe,SAAQ,YAAY;IAC/C,SAAS,CAAC,QAAQ,CAAC,QAAQ,OAAO;IAClC,SAAS,CAAC,QAAQ,CAAC,YAAY,sCAAsC;IACrE,SAAS,CAAC,QAAQ,CAAC,cAAc,sCAAsC;IACvE,SAAS,CAAC,QAAQ,CAAC,WAAW,kCAAkC;IAChE,SAAS,CAAC,QAAQ,CAAC,aAAa,uDAIrB;IACX,mBAA4B,YAAY,QAAQ;IAChD,2DAA2D;IAC3D,mBAA4B,SAAS,EAAG,OAAO,CAAU;cAEtC,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;cAKxC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW;CAsBrE"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * X (Twitter) OAuth2 driver.
3
+ *
4
+ * X requires PKCE, so this driver refuses to build a redirect without a code
5
+ * verifier. Mint one with `createCodeVerifier()`, store it beside the state,
6
+ * and hand it back to `callback()`.
7
+ */
8
+ import { Oauth2Driver } from "../Oauth2Driver.js";
9
+ export class TwitterXDriver extends Oauth2Driver {
10
+ provider = "X";
11
+ authorizeUrl = "https://x.com/i/oauth2/authorize";
12
+ accessTokenUrl = "https://api.x.com/2/oauth2/token";
13
+ userInfoUrl = "https://api.x.com/2/users/me";
14
+ defaultScopes = [
15
+ "tweet.read",
16
+ "users.read",
17
+ "users.email",
18
+ ];
19
+ requiresPkce = true;
20
+ /** X accepts the client credentials only as HTTP Basic. */
21
+ tokenAuth = "basic";
22
+ userInfoParams() {
23
+ // Neither the picture nor the address is returned unless asked for.
24
+ return { "user.fields": "profile_image_url,confirmed_email" };
25
+ }
26
+ mapUser(raw) {
27
+ // The profile arrives wrapped: `{ data: { ... } }`.
28
+ const data = typeof raw.data === "object" && raw.data !== null
29
+ ? raw.data
30
+ : raw;
31
+ return {
32
+ id: String(data.id ?? ""),
33
+ email: String(data.confirmed_email ?? ""),
34
+ name: String(data.name ?? data.username ?? ""),
35
+ nickName: typeof data.username === "string" ? data.username : undefined,
36
+ avatarUrl: typeof data.profile_image_url === "string" &&
37
+ data.profile_image_url !== ""
38
+ ? data.profile_image_url
39
+ : undefined,
40
+ // The field is named `confirmed_email`, but X publishes no separate
41
+ // verification flag, so nothing here can be claimed.
42
+ emailVerificationState: "unsupported",
43
+ raw,
44
+ };
45
+ }
46
+ }
47
+ //# sourceMappingURL=TwitterXDriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TwitterXDriver.js","sourceRoot":"","sources":["../../src/drivers/TwitterXDriver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,OAAO,cAAe,SAAQ,YAAY;IAC5B,QAAQ,GAAG,GAAG,CAAC;IACf,YAAY,GAAG,kCAAkC,CAAC;IAClD,cAAc,GAAG,kCAAkC,CAAC;IACpD,WAAW,GAAG,8BAA8B,CAAC;IAC7C,aAAa,GAAG;QAClC,YAAY;QACZ,YAAY;QACZ,aAAa;KACJ,CAAC;IACiB,YAAY,GAAG,IAAI,CAAC;IAChD,2DAA2D;IAC/B,SAAS,GAAG,OAAgB,CAAC;IAEtC,cAAc;QAChC,oEAAoE;QACpE,OAAO,EAAE,aAAa,EAAE,mCAAmC,EAAE,CAAC;IAC/D,CAAC;IAEkB,OAAO,CAAC,GAA4B;QACtD,oDAAoD;QACpD,MAAM,IAAI,GACT,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI;YAChD,CAAC,CAAE,GAAG,CAAC,IAAgC;YACvC,CAAC,CAAC,GAAG,CAAC;QACR,OAAO;YACN,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;YACzB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC;YACzC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;YAC9C,QAAQ,EAAE,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;YACvE,SAAS,EACR,OAAO,IAAI,CAAC,iBAAiB,KAAK,QAAQ;gBAC1C,IAAI,CAAC,iBAAiB,KAAK,EAAE;gBAC5B,CAAC,CAAC,IAAI,CAAC,iBAAiB;gBACxB,CAAC,CAAC,SAAS;YACb,oEAAoE;YACpE,qDAAqD;YACrD,sBAAsB,EAAE,aAAa;YACrC,GAAG;SACH,CAAC;IACH,CAAC;CACD"}
package/dist/dsig.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * XML Signature verification.
3
+ *
4
+ * The danger in XML-DSig is not the cryptography — it is that a signature can
5
+ * be perfectly valid over one part of a document while the application reads
6
+ * another. That is XML Signature Wrapping, and it has broken SAML
7
+ * implementations repeatedly. The mitigation is structural, not a check:
8
+ *
9
+ * **This returns the element the signature actually covers, and the caller
10
+ * must read that object. Never search the document again afterwards.**
11
+ *
12
+ * Everything else here exists to make that guarantee hold:
13
+ *
14
+ * - Exactly one reference. Several is not a shape a signed assertion needs,
15
+ * and it multiplies what "the signed element" could mean.
16
+ * - The reference must resolve to exactly ONE element. A second element
17
+ * carrying the same ID is the classic wrapping payload.
18
+ * - The signature must sit inside the element it signs. A signature that
19
+ * references a sibling is describing a document this refuses to reason
20
+ * about.
21
+ * - Only the enveloped-signature and exclusive-c14n transforms are accepted.
22
+ * XPath and XSLT transforms are a way to make the digest cover something
23
+ * other than the element, and XSLT is executable.
24
+ * - SHA-1 is refused. Collisions against it are practical, and a signature
25
+ * over attacker-influenced XML is exactly where that matters.
26
+ * - The key comes from the caller, from the provider's metadata. The
27
+ * certificate embedded in the document is checked against it, never
28
+ * trusted on its own: verifying a document against a key it carries is
29
+ * verifying it against itself.
30
+ */
31
+ import { type XmlElement } from "./xml.js";
32
+ export declare const DSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
33
+ export declare class SignatureError extends Error {
34
+ constructor(message: string);
35
+ }
36
+ export interface VerifyOptions {
37
+ /**
38
+ * The provider's signing certificates, from its metadata — PEM, or the
39
+ * bare base64 an `<X509Certificate>` element carries. Several are accepted
40
+ * so a rotation can be prepared before it happens.
41
+ */
42
+ certificates: string[];
43
+ }
44
+ /**
45
+ * Verify one signature under `root`, and answer with the element it covers.
46
+ *
47
+ * Read the returned element. Reading anything found by searching the document
48
+ * afterwards throws the guarantee away.
49
+ */
50
+ export declare function verifyXmlSignature(root: XmlElement, options: VerifyOptions): XmlElement;
51
+ //# sourceMappingURL=dsig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsig.d.ts","sourceRoot":"","sources":["../src/dsig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH,OAAO,EAIN,KAAK,UAAU,EAEf,MAAM,UAAU,CAAC;AAElB,eAAO,MAAM,OAAO,uCAAuC,CAAC;AAyC5D,qBAAa,cAAe,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI3B;AAED,MAAM,WAAW,aAAa;IAC7B;;;;OAIG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,aAAa,GACpB,UAAU,CAkEZ"}