@c9up/warden 0.1.22 → 0.1.23
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/README.md +1 -1
- package/dist/AuthManager.d.ts +14 -3
- package/dist/AuthManager.d.ts.map +1 -1
- package/dist/AuthManager.js +32 -4
- package/dist/AuthManager.js.map +1 -1
- package/dist/Authenticator.d.ts +48 -4
- package/dist/Authenticator.d.ts.map +1 -1
- package/dist/Authenticator.js +234 -25
- package/dist/Authenticator.js.map +1 -1
- package/dist/config.d.ts +8 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +3 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +13 -0
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js.map +1 -1
- package/dist/strategies/SessionStrategy.d.ts +37 -25
- package/dist/strategies/SessionStrategy.d.ts.map +1 -1
- package/dist/strategies/SessionStrategy.js +43 -44
- package/dist/strategies/SessionStrategy.js.map +1 -1
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
- package/src/AuthManager.ts +68 -6
- package/src/Authenticator.ts +298 -27
- package/src/config.ts +8 -1
- package/src/index.ts +9 -11
- package/src/middleware.ts +17 -0
- package/src/strategies/SessionStrategy.ts +73 -46
- package/dist/firstcontact/FirstContactManager.d.ts +0 -25
- package/dist/firstcontact/FirstContactManager.d.ts.map +0 -1
- package/dist/firstcontact/FirstContactManager.js +0 -38
- package/dist/firstcontact/FirstContactManager.js.map +0 -1
- package/dist/firstcontact/drivers/GitHubDriver.d.ts +0 -14
- package/dist/firstcontact/drivers/GitHubDriver.d.ts.map +0 -1
- package/dist/firstcontact/drivers/GitHubDriver.js +0 -60
- package/dist/firstcontact/drivers/GitHubDriver.js.map +0 -1
- package/dist/firstcontact/drivers/GoogleDriver.d.ts +0 -14
- package/dist/firstcontact/drivers/GoogleDriver.d.ts.map +0 -1
- package/dist/firstcontact/drivers/GoogleDriver.js +0 -63
- package/dist/firstcontact/drivers/GoogleDriver.js.map +0 -1
- package/dist/firstcontact/types.d.ts +0 -44
- package/dist/firstcontact/types.d.ts.map +0 -1
- package/dist/firstcontact/types.js +0 -22
- package/dist/firstcontact/types.js.map +0 -1
- package/src/firstcontact/FirstContactManager.ts +0 -54
- package/src/firstcontact/drivers/GitHubDriver.ts +0 -77
- package/src/firstcontact/drivers/GoogleDriver.ts +0 -80
- package/src/firstcontact/types.ts +0 -62
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FirstContactManager — OAuth2 social authentication.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* firstContact.use('google').redirectUrl()
|
|
6
|
-
* firstContact.use('google').callback(code)
|
|
7
|
-
*/
|
|
8
|
-
export class FirstContactManager {
|
|
9
|
-
#drivers = new Map();
|
|
10
|
-
use(name) {
|
|
11
|
-
const driver = this.#drivers.get(name);
|
|
12
|
-
if (!driver)
|
|
13
|
-
throw new Error(`OAuth driver '${name}' not registered. Available: ${[...this.#drivers.keys()].join(", ")}`);
|
|
14
|
-
return driver;
|
|
15
|
-
}
|
|
16
|
-
register(name, driver) {
|
|
17
|
-
this.#drivers.set(name, driver);
|
|
18
|
-
}
|
|
19
|
-
redirect(name, state) {
|
|
20
|
-
return this.use(name).redirectUrl(state);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Handle the OAuth callback. Pass `state` (from the query string) and
|
|
24
|
-
* `expectedState` (from the session, stored at redirect time) for CSRF
|
|
25
|
-
* protection. Omitting `expectedState` logs a security warning.
|
|
26
|
-
*/
|
|
27
|
-
async callback(name, code, state, expectedState) {
|
|
28
|
-
if (!expectedState) {
|
|
29
|
-
throw new Error(`[warden] OAuth callback for '${name}' requires expectedState for CSRF protection. ` +
|
|
30
|
-
`Store the state from redirect() in the session and pass it here.`);
|
|
31
|
-
}
|
|
32
|
-
return this.use(name).callback(code, state, expectedState);
|
|
33
|
-
}
|
|
34
|
-
get registeredDrivers() {
|
|
35
|
-
return [...this.#drivers.keys()];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
//# sourceMappingURL=FirstContactManager.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FirstContactManager.js","sourceRoot":"","sources":["../../src/firstcontact/FirstContactManager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,OAAO,mBAAmB;IAC/B,QAAQ,GAAoC,IAAI,GAAG,EAAE,CAAC;IAEtD,GAAG,CAAC,IAAY;QACf,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM;YACV,MAAM,IAAI,KAAK,CACd,iBAAiB,IAAI,gCAAgC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3F,CAAC;QACH,OAAO,MAAM,CAAC;IACf,CAAC;IAED,QAAQ,CAAC,IAAY,EAAE,MAA0B;QAChD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,QAAQ,CAAC,IAAY,EAAE,KAAc;QACpC,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CACb,IAAY,EACZ,IAAY,EACZ,KAAc,EACd,aAAsB;QAEtB,IAAI,CAAC,aAAa,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACd,gCAAgC,IAAI,gDAAgD;gBACnF,kEAAkE,CACnE,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,iBAAiB;QACpB,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC;CACD"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub OAuth2 driver for FirstContact.
|
|
3
|
-
*/
|
|
4
|
-
import type { FirstContactDriver, OAuthConfig, OAuthToken, OAuthUser } from "../types.js";
|
|
5
|
-
export declare class GitHubDriver implements FirstContactDriver {
|
|
6
|
-
private config;
|
|
7
|
-
constructor(config: OAuthConfig);
|
|
8
|
-
redirectUrl(state?: string): string;
|
|
9
|
-
callback(code: string, state?: string, expectedState?: string): Promise<{
|
|
10
|
-
user: OAuthUser;
|
|
11
|
-
token: OAuthToken;
|
|
12
|
-
}>;
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=GitHubDriver.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubDriver.d.ts","sourceRoot":"","sources":["../../../src/firstcontact/drivers/GitHubDriver.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACX,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,SAAS,EACT,MAAM,aAAa,CAAC;AAGrB,qBAAa,YAAa,YAAW,kBAAkB;IAC1C,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,WAAW;IAEvC,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAU7B,QAAQ,CACb,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC;CA+ClD"}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub OAuth2 driver for FirstContact.
|
|
3
|
-
*/
|
|
4
|
-
import { assertOAuthState } from "../types.js";
|
|
5
|
-
export class GitHubDriver {
|
|
6
|
-
config;
|
|
7
|
-
constructor(config) {
|
|
8
|
-
this.config = config;
|
|
9
|
-
}
|
|
10
|
-
redirectUrl(state) {
|
|
11
|
-
const params = new URLSearchParams({
|
|
12
|
-
client_id: this.config.clientId,
|
|
13
|
-
redirect_uri: this.config.callbackUrl,
|
|
14
|
-
scope: (this.config.scopes ?? ["read:user", "user:email"]).join(" "),
|
|
15
|
-
...(state ? { state } : {}),
|
|
16
|
-
});
|
|
17
|
-
return `https://github.com/login/oauth/authorize?${params}`;
|
|
18
|
-
}
|
|
19
|
-
async callback(code, state, expectedState) {
|
|
20
|
-
assertOAuthState(state, expectedState);
|
|
21
|
-
const tokenRes = await fetch("https://github.com/login/oauth/access_token", {
|
|
22
|
-
method: "POST",
|
|
23
|
-
headers: {
|
|
24
|
-
Accept: "application/json",
|
|
25
|
-
"Content-Type": "application/json",
|
|
26
|
-
},
|
|
27
|
-
body: JSON.stringify({
|
|
28
|
-
client_id: this.config.clientId,
|
|
29
|
-
client_secret: this.config.clientSecret,
|
|
30
|
-
code,
|
|
31
|
-
}),
|
|
32
|
-
});
|
|
33
|
-
if (!tokenRes.ok) {
|
|
34
|
-
throw new Error(`GitHub OAuth token exchange failed (HTTP ${tokenRes.status})`);
|
|
35
|
-
}
|
|
36
|
-
const tokens = (await tokenRes.json());
|
|
37
|
-
if (!tokens.access_token)
|
|
38
|
-
throw new Error("GitHub OAuth: no access_token in response");
|
|
39
|
-
const userRes = await fetch("https://api.github.com/user", {
|
|
40
|
-
headers: {
|
|
41
|
-
Authorization: `Bearer ${tokens.access_token}`,
|
|
42
|
-
Accept: "application/json",
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
if (!userRes.ok)
|
|
46
|
-
throw new Error(`GitHub user API failed (${userRes.status})`);
|
|
47
|
-
const raw = (await userRes.json());
|
|
48
|
-
return {
|
|
49
|
-
user: {
|
|
50
|
-
id: String(raw.id ?? ""),
|
|
51
|
-
email: String(raw.email ?? ""),
|
|
52
|
-
name: String(raw.name ?? raw.login ?? ""),
|
|
53
|
-
avatarUrl: raw.avatar_url,
|
|
54
|
-
raw,
|
|
55
|
-
},
|
|
56
|
-
token: { accessToken: String(tokens.access_token) },
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
//# sourceMappingURL=GitHubDriver.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubDriver.js","sourceRoot":"","sources":["../../../src/firstcontact/drivers/GitHubDriver.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,OAAO,YAAY;IACJ;IAApB,YAAoB,MAAmB;QAAnB,WAAM,GAAN,MAAM,CAAa;IAAG,CAAC;IAE3C,WAAW,CAAC,KAAc;QACzB,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YAClC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC/B,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;YACrC,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YACpE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3B,CAAC,CAAC;QACH,OAAO,4CAA4C,MAAM,EAAE,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,QAAQ,CACb,IAAY,EACZ,KAAc,EACd,aAAsB;QAEtB,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC3B,6CAA6C,EAC7C;YACC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACR,MAAM,EAAE,kBAAkB;gBAC1B,cAAc,EAAE,kBAAkB;aAClC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACpB,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAC/B,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;gBACvC,IAAI;aACJ,CAAC;SACF,CACD,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACd,4CAA4C,QAAQ,CAAC,MAAM,GAAG,CAC9D,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAC;QAClE,IAAI,CAAC,MAAM,CAAC,YAAY;YACvB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAE9D,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,6BAA6B,EAAE;YAC1D,OAAO,EAAE;gBACR,aAAa,EAAE,UAAU,MAAM,CAAC,YAAY,EAAE;gBAC9C,MAAM,EAAE,kBAAkB;aAC1B;SACD,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,EAAE;YACd,MAAM,IAAI,KAAK,CAAC,2BAA2B,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/D,MAAM,GAAG,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,CAA4B,CAAC;QAE9D,OAAO;YACN,IAAI,EAAE;gBACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;gBACxB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC9B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;gBACzC,SAAS,EAAE,GAAG,CAAC,UAAgC;gBAC/C,GAAG;aACH;YACD,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE;SACnD,CAAC;IACH,CAAC;CACD"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Google OAuth2 driver for FirstContact.
|
|
3
|
-
*/
|
|
4
|
-
import type { FirstContactDriver, OAuthConfig, OAuthToken, OAuthUser } from "../types.js";
|
|
5
|
-
export declare class GoogleDriver implements FirstContactDriver {
|
|
6
|
-
private config;
|
|
7
|
-
constructor(config: OAuthConfig);
|
|
8
|
-
redirectUrl(state?: string): string;
|
|
9
|
-
callback(code: string, state?: string, expectedState?: string): Promise<{
|
|
10
|
-
user: OAuthUser;
|
|
11
|
-
token: OAuthToken;
|
|
12
|
-
}>;
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=GoogleDriver.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleDriver.d.ts","sourceRoot":"","sources":["../../../src/firstcontact/drivers/GoogleDriver.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACX,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,SAAS,EACT,MAAM,aAAa,CAAC;AAGrB,qBAAa,YAAa,YAAW,kBAAkB;IAC1C,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,WAAW;IAEvC,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAY7B,QAAQ,CACb,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC;CAgDlD"}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Google OAuth2 driver for FirstContact.
|
|
3
|
-
*/
|
|
4
|
-
import { assertOAuthState } from "../types.js";
|
|
5
|
-
export class GoogleDriver {
|
|
6
|
-
config;
|
|
7
|
-
constructor(config) {
|
|
8
|
-
this.config = config;
|
|
9
|
-
}
|
|
10
|
-
redirectUrl(state) {
|
|
11
|
-
const params = new URLSearchParams({
|
|
12
|
-
client_id: this.config.clientId,
|
|
13
|
-
redirect_uri: this.config.callbackUrl,
|
|
14
|
-
response_type: "code",
|
|
15
|
-
scope: (this.config.scopes ?? ["openid", "email", "profile"]).join(" "),
|
|
16
|
-
access_type: "offline",
|
|
17
|
-
...(state ? { state } : {}),
|
|
18
|
-
});
|
|
19
|
-
return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
|
|
20
|
-
}
|
|
21
|
-
async callback(code, state, expectedState) {
|
|
22
|
-
// CSRF protection: validate state matches what we sent in redirectUrl().
|
|
23
|
-
assertOAuthState(state, expectedState);
|
|
24
|
-
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
25
|
-
method: "POST",
|
|
26
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
27
|
-
body: new URLSearchParams({
|
|
28
|
-
code,
|
|
29
|
-
client_id: this.config.clientId,
|
|
30
|
-
client_secret: this.config.clientSecret,
|
|
31
|
-
redirect_uri: this.config.callbackUrl,
|
|
32
|
-
grant_type: "authorization_code",
|
|
33
|
-
}),
|
|
34
|
-
});
|
|
35
|
-
if (!tokenRes.ok) {
|
|
36
|
-
throw new Error(`Google OAuth token exchange failed (HTTP ${tokenRes.status})`);
|
|
37
|
-
}
|
|
38
|
-
const tokens = (await tokenRes.json());
|
|
39
|
-
if (!tokens.access_token)
|
|
40
|
-
throw new Error("Google OAuth: no access_token in response");
|
|
41
|
-
const userRes = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
|
|
42
|
-
headers: { Authorization: `Bearer ${tokens.access_token}` },
|
|
43
|
-
});
|
|
44
|
-
if (!userRes.ok)
|
|
45
|
-
throw new Error(`Google userinfo failed (${userRes.status})`);
|
|
46
|
-
const raw = (await userRes.json());
|
|
47
|
-
return {
|
|
48
|
-
user: {
|
|
49
|
-
id: String(raw.id ?? ""),
|
|
50
|
-
email: String(raw.email ?? ""),
|
|
51
|
-
name: String(raw.name ?? ""),
|
|
52
|
-
avatarUrl: raw.picture,
|
|
53
|
-
raw,
|
|
54
|
-
},
|
|
55
|
-
token: {
|
|
56
|
-
accessToken: String(tokens.access_token),
|
|
57
|
-
refreshToken: tokens.refresh_token,
|
|
58
|
-
expiresIn: tokens.expires_in,
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
//# sourceMappingURL=GoogleDriver.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleDriver.js","sourceRoot":"","sources":["../../../src/firstcontact/drivers/GoogleDriver.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,OAAO,YAAY;IACJ;IAApB,YAAoB,MAAmB;QAAnB,WAAM,GAAN,MAAM,CAAa;IAAG,CAAC;IAE3C,WAAW,CAAC,KAAc;QACzB,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YAClC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC/B,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;YACrC,aAAa,EAAE,MAAM;YACrB,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YACvE,WAAW,EAAE,SAAS;YACtB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3B,CAAC,CAAC;QACH,OAAO,gDAAgD,MAAM,EAAE,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,QAAQ,CACb,IAAY,EACZ,KAAc,EACd,aAAsB;QAEtB,yEAAyE;QACzE,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,qCAAqC,EAAE;YACnE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;YAChE,IAAI,EAAE,IAAI,eAAe,CAAC;gBACzB,IAAI;gBACJ,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAC/B,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;gBACvC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;gBACrC,UAAU,EAAE,oBAAoB;aAChC,CAAC;SACF,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACd,4CAA4C,QAAQ,CAAC,MAAM,GAAG,CAC9D,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAC;QAClE,IAAI,CAAC,MAAM,CAAC,YAAY;YACvB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAE9D,MAAM,OAAO,GAAG,MAAM,KAAK,CAC1B,+CAA+C,EAC/C;YACC,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,CAAC,YAAY,EAAE,EAAE;SAC3D,CACD,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,EAAE;YACd,MAAM,IAAI,KAAK,CAAC,2BAA2B,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/D,MAAM,GAAG,GAAG,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,CAA4B,CAAC;QAE9D,OAAO;YACN,IAAI,EAAE;gBACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;gBACxB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC9B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC5B,SAAS,EAAE,GAAG,CAAC,OAA6B;gBAC5C,GAAG;aACH;YACD,KAAK,EAAE;gBACN,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;gBACxC,YAAY,EAAE,MAAM,CAAC,aAAmC;gBACxD,SAAS,EAAE,MAAM,CAAC,UAAgC;aAClD;SACD,CAAC;IACH,CAAC;CACD"}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FirstContact — OAuth2 social authentication types.
|
|
3
|
-
*/
|
|
4
|
-
export interface OAuthConfig {
|
|
5
|
-
clientId: string;
|
|
6
|
-
clientSecret: string;
|
|
7
|
-
callbackUrl: string;
|
|
8
|
-
scopes?: string[];
|
|
9
|
-
}
|
|
10
|
-
export interface OAuthUser {
|
|
11
|
-
id: string;
|
|
12
|
-
email: string;
|
|
13
|
-
name: string;
|
|
14
|
-
avatarUrl?: string;
|
|
15
|
-
raw: Record<string, unknown>;
|
|
16
|
-
}
|
|
17
|
-
export interface OAuthToken {
|
|
18
|
-
accessToken: string;
|
|
19
|
-
refreshToken?: string;
|
|
20
|
-
expiresIn?: number;
|
|
21
|
-
}
|
|
22
|
-
export interface FirstContactDriver {
|
|
23
|
-
redirectUrl(state?: string): string;
|
|
24
|
-
/**
|
|
25
|
-
* Handle the OAuth callback. `state` MUST be validated against the value
|
|
26
|
-
* originally passed to `redirectUrl()` to prevent CSRF login attacks.
|
|
27
|
-
* Throws if state is missing or mismatched.
|
|
28
|
-
*/
|
|
29
|
-
callback(code: string, state?: string, expectedState?: string): Promise<{
|
|
30
|
-
user: OAuthUser;
|
|
31
|
-
token: OAuthToken;
|
|
32
|
-
}>;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Check the OAuth `state` round-trip, failing CLOSED.
|
|
36
|
-
*
|
|
37
|
-
* The check used to be `if (expectedState && state !== expectedState)`, so a
|
|
38
|
-
* caller that passed no expected state got NO CSRF protection and no sign that
|
|
39
|
-
* it was missing. A driver used directly — without the manager, which already
|
|
40
|
-
* refuses — was therefore open by default. Anything that cannot be verified is
|
|
41
|
-
* refused instead.
|
|
42
|
-
*/
|
|
43
|
-
export declare function assertOAuthState(state: string | undefined, expectedState: string | undefined): void;
|
|
44
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/firstcontact/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IAClC,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CACP,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;CACnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,aAAa,EAAE,MAAM,GAAG,SAAS,GAC/B,IAAI,CAUN"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FirstContact — OAuth2 social authentication types.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Check the OAuth `state` round-trip, failing CLOSED.
|
|
6
|
-
*
|
|
7
|
-
* The check used to be `if (expectedState && state !== expectedState)`, so a
|
|
8
|
-
* caller that passed no expected state got NO CSRF protection and no sign that
|
|
9
|
-
* it was missing. A driver used directly — without the manager, which already
|
|
10
|
-
* refuses — was therefore open by default. Anything that cannot be verified is
|
|
11
|
-
* refused instead.
|
|
12
|
-
*/
|
|
13
|
-
export function assertOAuthState(state, expectedState) {
|
|
14
|
-
if (!expectedState) {
|
|
15
|
-
throw new Error("[warden] OAuth callback requires expectedState for CSRF protection. " +
|
|
16
|
-
"Store the state given to redirectUrl() in the session and pass it here.");
|
|
17
|
-
}
|
|
18
|
-
if (state !== expectedState) {
|
|
19
|
-
throw new Error("OAuth state mismatch — possible CSRF attack");
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
//# sourceMappingURL=types.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/firstcontact/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAqCH;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC/B,KAAyB,EACzB,aAAiC;IAEjC,IAAI,CAAC,aAAa,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACd,sEAAsE;YACrE,yEAAyE,CAC1E,CAAC;IACH,CAAC;IACD,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAChE,CAAC;AACF,CAAC"}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FirstContactManager — OAuth2 social authentication.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* firstContact.use('google').redirectUrl()
|
|
6
|
-
* firstContact.use('google').callback(code)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { FirstContactDriver, OAuthToken, OAuthUser } from "./types.js";
|
|
10
|
-
|
|
11
|
-
export class FirstContactManager {
|
|
12
|
-
#drivers: Map<string, FirstContactDriver> = new Map();
|
|
13
|
-
|
|
14
|
-
use(name: string): FirstContactDriver {
|
|
15
|
-
const driver = this.#drivers.get(name);
|
|
16
|
-
if (!driver)
|
|
17
|
-
throw new Error(
|
|
18
|
-
`OAuth driver '${name}' not registered. Available: ${[...this.#drivers.keys()].join(", ")}`,
|
|
19
|
-
);
|
|
20
|
-
return driver;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
register(name: string, driver: FirstContactDriver): void {
|
|
24
|
-
this.#drivers.set(name, driver);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
redirect(name: string, state?: string): string {
|
|
28
|
-
return this.use(name).redirectUrl(state);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Handle the OAuth callback. Pass `state` (from the query string) and
|
|
33
|
-
* `expectedState` (from the session, stored at redirect time) for CSRF
|
|
34
|
-
* protection. Omitting `expectedState` logs a security warning.
|
|
35
|
-
*/
|
|
36
|
-
async callback(
|
|
37
|
-
name: string,
|
|
38
|
-
code: string,
|
|
39
|
-
state?: string,
|
|
40
|
-
expectedState?: string,
|
|
41
|
-
): Promise<{ user: OAuthUser; token: OAuthToken }> {
|
|
42
|
-
if (!expectedState) {
|
|
43
|
-
throw new Error(
|
|
44
|
-
`[warden] OAuth callback for '${name}' requires expectedState for CSRF protection. ` +
|
|
45
|
-
`Store the state from redirect() in the session and pass it here.`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
return this.use(name).callback(code, state, expectedState);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
get registeredDrivers(): string[] {
|
|
52
|
-
return [...this.#drivers.keys()];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub OAuth2 driver for FirstContact.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
FirstContactDriver,
|
|
7
|
-
OAuthConfig,
|
|
8
|
-
OAuthToken,
|
|
9
|
-
OAuthUser,
|
|
10
|
-
} from "../types.js";
|
|
11
|
-
import { assertOAuthState } from "../types.js";
|
|
12
|
-
|
|
13
|
-
export class GitHubDriver implements FirstContactDriver {
|
|
14
|
-
constructor(private config: OAuthConfig) {}
|
|
15
|
-
|
|
16
|
-
redirectUrl(state?: string): string {
|
|
17
|
-
const params = new URLSearchParams({
|
|
18
|
-
client_id: this.config.clientId,
|
|
19
|
-
redirect_uri: this.config.callbackUrl,
|
|
20
|
-
scope: (this.config.scopes ?? ["read:user", "user:email"]).join(" "),
|
|
21
|
-
...(state ? { state } : {}),
|
|
22
|
-
});
|
|
23
|
-
return `https://github.com/login/oauth/authorize?${params}`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async callback(
|
|
27
|
-
code: string,
|
|
28
|
-
state?: string,
|
|
29
|
-
expectedState?: string,
|
|
30
|
-
): Promise<{ user: OAuthUser; token: OAuthToken }> {
|
|
31
|
-
assertOAuthState(state, expectedState);
|
|
32
|
-
const tokenRes = await fetch(
|
|
33
|
-
"https://github.com/login/oauth/access_token",
|
|
34
|
-
{
|
|
35
|
-
method: "POST",
|
|
36
|
-
headers: {
|
|
37
|
-
Accept: "application/json",
|
|
38
|
-
"Content-Type": "application/json",
|
|
39
|
-
},
|
|
40
|
-
body: JSON.stringify({
|
|
41
|
-
client_id: this.config.clientId,
|
|
42
|
-
client_secret: this.config.clientSecret,
|
|
43
|
-
code,
|
|
44
|
-
}),
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
if (!tokenRes.ok) {
|
|
48
|
-
throw new Error(
|
|
49
|
-
`GitHub OAuth token exchange failed (HTTP ${tokenRes.status})`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
const tokens = (await tokenRes.json()) as Record<string, unknown>;
|
|
53
|
-
if (!tokens.access_token)
|
|
54
|
-
throw new Error("GitHub OAuth: no access_token in response");
|
|
55
|
-
|
|
56
|
-
const userRes = await fetch("https://api.github.com/user", {
|
|
57
|
-
headers: {
|
|
58
|
-
Authorization: `Bearer ${tokens.access_token}`,
|
|
59
|
-
Accept: "application/json",
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
if (!userRes.ok)
|
|
63
|
-
throw new Error(`GitHub user API failed (${userRes.status})`);
|
|
64
|
-
const raw = (await userRes.json()) as Record<string, unknown>;
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
user: {
|
|
68
|
-
id: String(raw.id ?? ""),
|
|
69
|
-
email: String(raw.email ?? ""),
|
|
70
|
-
name: String(raw.name ?? raw.login ?? ""),
|
|
71
|
-
avatarUrl: raw.avatar_url as string | undefined,
|
|
72
|
-
raw,
|
|
73
|
-
},
|
|
74
|
-
token: { accessToken: String(tokens.access_token) },
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Google OAuth2 driver for FirstContact.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
FirstContactDriver,
|
|
7
|
-
OAuthConfig,
|
|
8
|
-
OAuthToken,
|
|
9
|
-
OAuthUser,
|
|
10
|
-
} from "../types.js";
|
|
11
|
-
import { assertOAuthState } from "../types.js";
|
|
12
|
-
|
|
13
|
-
export class GoogleDriver implements FirstContactDriver {
|
|
14
|
-
constructor(private config: OAuthConfig) {}
|
|
15
|
-
|
|
16
|
-
redirectUrl(state?: string): string {
|
|
17
|
-
const params = new URLSearchParams({
|
|
18
|
-
client_id: this.config.clientId,
|
|
19
|
-
redirect_uri: this.config.callbackUrl,
|
|
20
|
-
response_type: "code",
|
|
21
|
-
scope: (this.config.scopes ?? ["openid", "email", "profile"]).join(" "),
|
|
22
|
-
access_type: "offline",
|
|
23
|
-
...(state ? { state } : {}),
|
|
24
|
-
});
|
|
25
|
-
return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async callback(
|
|
29
|
-
code: string,
|
|
30
|
-
state?: string,
|
|
31
|
-
expectedState?: string,
|
|
32
|
-
): Promise<{ user: OAuthUser; token: OAuthToken }> {
|
|
33
|
-
// CSRF protection: validate state matches what we sent in redirectUrl().
|
|
34
|
-
assertOAuthState(state, expectedState);
|
|
35
|
-
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
36
|
-
method: "POST",
|
|
37
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
38
|
-
body: new URLSearchParams({
|
|
39
|
-
code,
|
|
40
|
-
client_id: this.config.clientId,
|
|
41
|
-
client_secret: this.config.clientSecret,
|
|
42
|
-
redirect_uri: this.config.callbackUrl,
|
|
43
|
-
grant_type: "authorization_code",
|
|
44
|
-
}),
|
|
45
|
-
});
|
|
46
|
-
if (!tokenRes.ok) {
|
|
47
|
-
throw new Error(
|
|
48
|
-
`Google OAuth token exchange failed (HTTP ${tokenRes.status})`,
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
const tokens = (await tokenRes.json()) as Record<string, unknown>;
|
|
52
|
-
if (!tokens.access_token)
|
|
53
|
-
throw new Error("Google OAuth: no access_token in response");
|
|
54
|
-
|
|
55
|
-
const userRes = await fetch(
|
|
56
|
-
"https://www.googleapis.com/oauth2/v2/userinfo",
|
|
57
|
-
{
|
|
58
|
-
headers: { Authorization: `Bearer ${tokens.access_token}` },
|
|
59
|
-
},
|
|
60
|
-
);
|
|
61
|
-
if (!userRes.ok)
|
|
62
|
-
throw new Error(`Google userinfo failed (${userRes.status})`);
|
|
63
|
-
const raw = (await userRes.json()) as Record<string, unknown>;
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
user: {
|
|
67
|
-
id: String(raw.id ?? ""),
|
|
68
|
-
email: String(raw.email ?? ""),
|
|
69
|
-
name: String(raw.name ?? ""),
|
|
70
|
-
avatarUrl: raw.picture as string | undefined,
|
|
71
|
-
raw,
|
|
72
|
-
},
|
|
73
|
-
token: {
|
|
74
|
-
accessToken: String(tokens.access_token),
|
|
75
|
-
refreshToken: tokens.refresh_token as string | undefined,
|
|
76
|
-
expiresIn: tokens.expires_in as number | undefined,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FirstContact — OAuth2 social authentication types.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface OAuthConfig {
|
|
6
|
-
clientId: string;
|
|
7
|
-
clientSecret: string;
|
|
8
|
-
callbackUrl: string;
|
|
9
|
-
scopes?: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface OAuthUser {
|
|
13
|
-
id: string;
|
|
14
|
-
email: string;
|
|
15
|
-
name: string;
|
|
16
|
-
avatarUrl?: string;
|
|
17
|
-
raw: Record<string, unknown>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface OAuthToken {
|
|
21
|
-
accessToken: string;
|
|
22
|
-
refreshToken?: string;
|
|
23
|
-
expiresIn?: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface FirstContactDriver {
|
|
27
|
-
redirectUrl(state?: string): string;
|
|
28
|
-
/**
|
|
29
|
-
* Handle the OAuth callback. `state` MUST be validated against the value
|
|
30
|
-
* originally passed to `redirectUrl()` to prevent CSRF login attacks.
|
|
31
|
-
* Throws if state is missing or mismatched.
|
|
32
|
-
*/
|
|
33
|
-
callback(
|
|
34
|
-
code: string,
|
|
35
|
-
state?: string,
|
|
36
|
-
expectedState?: string,
|
|
37
|
-
): Promise<{ user: OAuthUser; token: OAuthToken }>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Check the OAuth `state` round-trip, failing CLOSED.
|
|
42
|
-
*
|
|
43
|
-
* The check used to be `if (expectedState && state !== expectedState)`, so a
|
|
44
|
-
* caller that passed no expected state got NO CSRF protection and no sign that
|
|
45
|
-
* it was missing. A driver used directly — without the manager, which already
|
|
46
|
-
* refuses — was therefore open by default. Anything that cannot be verified is
|
|
47
|
-
* refused instead.
|
|
48
|
-
*/
|
|
49
|
-
export function assertOAuthState(
|
|
50
|
-
state: string | undefined,
|
|
51
|
-
expectedState: string | undefined,
|
|
52
|
-
): void {
|
|
53
|
-
if (!expectedState) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
"[warden] OAuth callback requires expectedState for CSRF protection. " +
|
|
56
|
-
"Store the state given to redirectUrl() in the session and pass it here.",
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
if (state !== expectedState) {
|
|
60
|
-
throw new Error("OAuth state mismatch — possible CSRF attack");
|
|
61
|
-
}
|
|
62
|
-
}
|