@c9up/warden 0.1.21 → 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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/dist/AuthManager.d.ts +14 -3
  3. package/dist/AuthManager.d.ts.map +1 -1
  4. package/dist/AuthManager.js +32 -4
  5. package/dist/AuthManager.js.map +1 -1
  6. package/dist/Authenticator.d.ts +48 -4
  7. package/dist/Authenticator.d.ts.map +1 -1
  8. package/dist/Authenticator.js +234 -25
  9. package/dist/Authenticator.js.map +1 -1
  10. package/dist/config.d.ts +8 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/index.d.ts +3 -7
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/middleware.d.ts +13 -0
  18. package/dist/middleware.d.ts.map +1 -1
  19. package/dist/middleware.js.map +1 -1
  20. package/dist/native/generated.d.ts +8 -0
  21. package/dist/native/generated.d.ts.map +1 -0
  22. package/dist/native/generated.js +7 -0
  23. package/dist/native/generated.js.map +1 -0
  24. package/dist/native.d.ts +8 -9
  25. package/dist/native.d.ts.map +1 -1
  26. package/dist/native.js.map +1 -1
  27. package/dist/strategies/SessionStrategy.d.ts +37 -25
  28. package/dist/strategies/SessionStrategy.d.ts.map +1 -1
  29. package/dist/strategies/SessionStrategy.js +43 -44
  30. package/dist/strategies/SessionStrategy.js.map +1 -1
  31. package/index.win32-x64-msvc.node +0 -0
  32. package/package.json +3 -2
  33. package/scripts/build-napi-types.mjs +68 -0
  34. package/scripts/generate-napi-types.mjs +153 -0
  35. package/src/AuthManager.ts +68 -6
  36. package/src/Authenticator.ts +298 -27
  37. package/src/config.ts +8 -1
  38. package/src/index.ts +9 -11
  39. package/src/middleware.ts +17 -0
  40. package/src/native/generated.ts +23 -0
  41. package/src/native.ts +8 -9
  42. package/src/strategies/SessionStrategy.ts +73 -46
  43. package/dist/firstcontact/FirstContactManager.d.ts +0 -25
  44. package/dist/firstcontact/FirstContactManager.d.ts.map +0 -1
  45. package/dist/firstcontact/FirstContactManager.js +0 -38
  46. package/dist/firstcontact/FirstContactManager.js.map +0 -1
  47. package/dist/firstcontact/drivers/GitHubDriver.d.ts +0 -14
  48. package/dist/firstcontact/drivers/GitHubDriver.d.ts.map +0 -1
  49. package/dist/firstcontact/drivers/GitHubDriver.js +0 -60
  50. package/dist/firstcontact/drivers/GitHubDriver.js.map +0 -1
  51. package/dist/firstcontact/drivers/GoogleDriver.d.ts +0 -14
  52. package/dist/firstcontact/drivers/GoogleDriver.d.ts.map +0 -1
  53. package/dist/firstcontact/drivers/GoogleDriver.js +0 -63
  54. package/dist/firstcontact/drivers/GoogleDriver.js.map +0 -1
  55. package/dist/firstcontact/types.d.ts +0 -44
  56. package/dist/firstcontact/types.d.ts.map +0 -1
  57. package/dist/firstcontact/types.js +0 -22
  58. package/dist/firstcontact/types.js.map +0 -1
  59. package/src/firstcontact/FirstContactManager.ts +0 -54
  60. package/src/firstcontact/drivers/GitHubDriver.ts +0 -77
  61. package/src/firstcontact/drivers/GoogleDriver.ts +0 -80
  62. package/src/firstcontact/types.ts +0 -62
@@ -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
- }