@authcore/core 0.7.0 → 0.12.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 (87) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +141 -125
  3. package/dist/auth.d.ts +140 -12
  4. package/dist/auth.d.ts.map +1 -1
  5. package/dist/auth.js +265 -7
  6. package/dist/auth.js.map +1 -1
  7. package/dist/features/emailVerification.d.ts +6 -1
  8. package/dist/features/emailVerification.d.ts.map +1 -1
  9. package/dist/features/emailVerification.js +7 -10
  10. package/dist/features/emailVerification.js.map +1 -1
  11. package/dist/features/invitation.d.ts +7 -1
  12. package/dist/features/invitation.d.ts.map +1 -1
  13. package/dist/features/invitation.js +7 -9
  14. package/dist/features/invitation.js.map +1 -1
  15. package/dist/features/magicLink.d.ts +56 -0
  16. package/dist/features/magicLink.d.ts.map +1 -0
  17. package/dist/features/magicLink.js +88 -0
  18. package/dist/features/magicLink.js.map +1 -0
  19. package/dist/features/oauth.d.ts +39 -0
  20. package/dist/features/oauth.d.ts.map +1 -0
  21. package/dist/features/oauth.js +161 -0
  22. package/dist/features/oauth.js.map +1 -0
  23. package/dist/features/passwordReset.d.ts +6 -1
  24. package/dist/features/passwordReset.d.ts.map +1 -1
  25. package/dist/features/passwordReset.js +7 -10
  26. package/dist/features/passwordReset.js.map +1 -1
  27. package/dist/features/refresh.d.ts +41 -0
  28. package/dist/features/refresh.d.ts.map +1 -0
  29. package/dist/features/refresh.js +58 -0
  30. package/dist/features/refresh.js.map +1 -0
  31. package/dist/features/templates.d.ts +46 -0
  32. package/dist/features/templates.d.ts.map +1 -0
  33. package/dist/features/templates.js +67 -0
  34. package/dist/features/templates.js.map +1 -0
  35. package/dist/features/twoFactor.d.ts +72 -0
  36. package/dist/features/twoFactor.d.ts.map +1 -0
  37. package/dist/features/twoFactor.js +119 -0
  38. package/dist/features/twoFactor.js.map +1 -0
  39. package/dist/index.d.ts +21 -5
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +13 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/oauth/apple.d.ts +80 -0
  44. package/dist/oauth/apple.d.ts.map +1 -0
  45. package/dist/oauth/apple.js +148 -0
  46. package/dist/oauth/apple.js.map +1 -0
  47. package/dist/oauth/discord.d.ts +32 -0
  48. package/dist/oauth/discord.d.ts.map +1 -0
  49. package/dist/oauth/discord.js +86 -0
  50. package/dist/oauth/discord.js.map +1 -0
  51. package/dist/oauth/github.d.ts +35 -0
  52. package/dist/oauth/github.d.ts.map +1 -0
  53. package/dist/oauth/github.js +114 -0
  54. package/dist/oauth/github.js.map +1 -0
  55. package/dist/oauth/google.d.ts +21 -0
  56. package/dist/oauth/google.d.ts.map +1 -0
  57. package/dist/oauth/google.js +76 -0
  58. package/dist/oauth/google.js.map +1 -0
  59. package/dist/oauth/microsoft.d.ts +40 -0
  60. package/dist/oauth/microsoft.d.ts.map +1 -0
  61. package/dist/oauth/microsoft.js +126 -0
  62. package/dist/oauth/microsoft.js.map +1 -0
  63. package/dist/utils/token.d.ts +37 -0
  64. package/dist/utils/token.d.ts.map +1 -1
  65. package/dist/utils/token.js +53 -0
  66. package/dist/utils/token.js.map +1 -1
  67. package/dist/utils/totp.d.ts +59 -0
  68. package/dist/utils/totp.d.ts.map +1 -0
  69. package/dist/utils/totp.js +176 -0
  70. package/dist/utils/totp.js.map +1 -0
  71. package/dist/utils/validation.d.ts +18 -0
  72. package/dist/utils/validation.d.ts.map +1 -1
  73. package/dist/utils/validation.js +8 -0
  74. package/dist/utils/validation.js.map +1 -1
  75. package/package.json +2 -2
  76. package/dist/adapters/database.interface.d.ts +0 -42
  77. package/dist/adapters/database.interface.d.ts.map +0 -1
  78. package/dist/adapters/database.interface.js +0 -2
  79. package/dist/adapters/database.interface.js.map +0 -1
  80. package/dist/adapters/email.interface.d.ts +0 -31
  81. package/dist/adapters/email.interface.d.ts.map +0 -1
  82. package/dist/adapters/email.interface.js +0 -2
  83. package/dist/adapters/email.interface.js.map +0 -1
  84. package/dist/types.d.ts +0 -76
  85. package/dist/types.d.ts.map +0 -1
  86. package/dist/types.js +0 -6
  87. package/dist/types.js.map +0 -1
@@ -0,0 +1,86 @@
1
+ const DEFAULT_SCOPES = ['identify', 'email'];
2
+ /**
3
+ * Create a Discord OAuth 2.0 provider.
4
+ *
5
+ * ```ts
6
+ * import { createDiscordProvider } from '@authcore/core'
7
+ * const discord = createDiscordProvider({
8
+ * clientId: process.env.DISCORD_CLIENT_ID!,
9
+ * clientSecret: process.env.DISCORD_CLIENT_SECRET!,
10
+ * })
11
+ * createAuth({ ..., oauth: { discord } })
12
+ * ```
13
+ *
14
+ * Notes:
15
+ * - Discord exposes `verified` on the user object — AuthCore threads that
16
+ * straight through to `emailVerified`. Unverified Discord users will hit
17
+ * the standard AuthCore "EMAIL_NOT_VERIFIED_BY_PROVIDER" gate when linking
18
+ * to an existing local account.
19
+ * - User avatar URLs are constructed from the user's `id` + `avatar` hash
20
+ * (https://discord.com/developers/docs/reference#image-formatting).
21
+ */
22
+ export function createDiscordProvider(config) {
23
+ const scopes = config.scopes ?? DEFAULT_SCOPES;
24
+ return {
25
+ id: 'discord',
26
+ scopes,
27
+ authorize: ({ state, codeChallenge, redirectUri }) => {
28
+ const url = new URL('https://discord.com/oauth2/authorize');
29
+ url.searchParams.set('client_id', config.clientId);
30
+ url.searchParams.set('redirect_uri', redirectUri);
31
+ url.searchParams.set('response_type', 'code');
32
+ url.searchParams.set('scope', scopes.join(' '));
33
+ url.searchParams.set('state', state);
34
+ url.searchParams.set('code_challenge', codeChallenge);
35
+ url.searchParams.set('code_challenge_method', 'S256');
36
+ // `prompt=consent` ensures Discord re-asks (vs. silently re-using a prior approval).
37
+ url.searchParams.set('prompt', 'consent');
38
+ return url.toString();
39
+ },
40
+ exchangeCode: async ({ code, codeVerifier, redirectUri }) => {
41
+ const res = await fetch('https://discord.com/api/oauth2/token', {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
44
+ body: new URLSearchParams({
45
+ code,
46
+ client_id: config.clientId,
47
+ client_secret: config.clientSecret,
48
+ redirect_uri: redirectUri,
49
+ grant_type: 'authorization_code',
50
+ code_verifier: codeVerifier,
51
+ }),
52
+ });
53
+ if (!res.ok) {
54
+ throw new Error(`Discord token exchange failed (${res.status}): ${await res.text()}`);
55
+ }
56
+ const body = (await res.json());
57
+ return {
58
+ accessToken: body.access_token,
59
+ ...(body.refresh_token !== undefined ? { refreshToken: body.refresh_token } : {}),
60
+ ...(body.expires_in !== undefined ? { expiresIn: body.expires_in } : {}),
61
+ };
62
+ },
63
+ getUserInfo: async (accessToken) => {
64
+ const res = await fetch('https://discord.com/api/users/@me', {
65
+ headers: { Authorization: `Bearer ${accessToken}` },
66
+ });
67
+ if (!res.ok) {
68
+ throw new Error(`Discord /users/@me failed (${res.status}): ${await res.text()}`);
69
+ }
70
+ const me = (await res.json());
71
+ if (!me.email) {
72
+ throw new Error('Discord account has no email (was the `email` scope requested?)');
73
+ }
74
+ return {
75
+ id: me.id,
76
+ email: me.email,
77
+ emailVerified: me.verified === true,
78
+ ...(me.global_name || me.username ? { name: me.global_name ?? me.username } : {}),
79
+ ...(me.avatar
80
+ ? { picture: `https://cdn.discordapp.com/avatars/${me.id}/${me.avatar}.png` }
81
+ : {}),
82
+ };
83
+ },
84
+ };
85
+ }
86
+ //# sourceMappingURL=discord.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord.js","sourceRoot":"","sources":["../../src/oauth/discord.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { OAuthProvider } from '@authcore/types';
2
+ export interface GithubProviderConfig {
3
+ clientId: string;
4
+ clientSecret: string;
5
+ /**
6
+ * Defaults to `['read:user', 'user:email']`. `user:email` is required to read
7
+ * the user's verified primary email — GitHub does not include email in the
8
+ * user object unless it has been marked public on the account.
9
+ */
10
+ scopes?: string[];
11
+ /** Optional GitHub Enterprise base URL (e.g. `https://ghe.example.com`). */
12
+ enterpriseBaseUrl?: string;
13
+ }
14
+ /**
15
+ * Create a GitHub OAuth 2.0 provider.
16
+ *
17
+ * ```ts
18
+ * import { createGithubProvider } from '@authcore/core'
19
+ * const github = createGithubProvider({
20
+ * clientId: process.env.GITHUB_CLIENT_ID!,
21
+ * clientSecret: process.env.GITHUB_CLIENT_SECRET!,
22
+ * })
23
+ * createAuth({ ..., oauth: { github } })
24
+ * ```
25
+ *
26
+ * Notes:
27
+ * - GitHub does not natively support PKCE on the OAuth Apps endpoint, but it
28
+ * accepts the `code_challenge`/`code_challenge_method` query params without
29
+ * error. AuthCore sends them anyway; GitHub OAuth Apps ignore them while
30
+ * GitHub Apps verify them when configured to.
31
+ * - The email returned is always the user's **verified primary** email. If
32
+ * they have no verified email, the provider throws — login is refused.
33
+ */
34
+ export declare function createGithubProvider(config: GithubProviderConfig): OAuthProvider;
35
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/oauth/github.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAID;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,aAAa,CAoHhF"}
@@ -0,0 +1,114 @@
1
+ const DEFAULT_SCOPES = ['read:user', 'user:email'];
2
+ /**
3
+ * Create a GitHub OAuth 2.0 provider.
4
+ *
5
+ * ```ts
6
+ * import { createGithubProvider } from '@authcore/core'
7
+ * const github = createGithubProvider({
8
+ * clientId: process.env.GITHUB_CLIENT_ID!,
9
+ * clientSecret: process.env.GITHUB_CLIENT_SECRET!,
10
+ * })
11
+ * createAuth({ ..., oauth: { github } })
12
+ * ```
13
+ *
14
+ * Notes:
15
+ * - GitHub does not natively support PKCE on the OAuth Apps endpoint, but it
16
+ * accepts the `code_challenge`/`code_challenge_method` query params without
17
+ * error. AuthCore sends them anyway; GitHub OAuth Apps ignore them while
18
+ * GitHub Apps verify them when configured to.
19
+ * - The email returned is always the user's **verified primary** email. If
20
+ * they have no verified email, the provider throws — login is refused.
21
+ */
22
+ export function createGithubProvider(config) {
23
+ const scopes = config.scopes ?? DEFAULT_SCOPES;
24
+ const oauthBase = config.enterpriseBaseUrl
25
+ ? `${config.enterpriseBaseUrl}/login/oauth`
26
+ : 'https://github.com/login/oauth';
27
+ const apiBase = config.enterpriseBaseUrl
28
+ ? `${config.enterpriseBaseUrl}/api/v3`
29
+ : 'https://api.github.com';
30
+ return {
31
+ id: 'github',
32
+ scopes,
33
+ authorize: ({ state, codeChallenge, redirectUri }) => {
34
+ const url = new URL(`${oauthBase}/authorize`);
35
+ url.searchParams.set('client_id', config.clientId);
36
+ url.searchParams.set('redirect_uri', redirectUri);
37
+ url.searchParams.set('scope', scopes.join(' '));
38
+ url.searchParams.set('state', state);
39
+ url.searchParams.set('code_challenge', codeChallenge);
40
+ url.searchParams.set('code_challenge_method', 'S256');
41
+ return url.toString();
42
+ },
43
+ exchangeCode: async ({ code, codeVerifier, redirectUri }) => {
44
+ const res = await fetch(`${oauthBase}/access_token`, {
45
+ method: 'POST',
46
+ headers: {
47
+ 'Content-Type': 'application/x-www-form-urlencoded',
48
+ Accept: 'application/json',
49
+ },
50
+ body: new URLSearchParams({
51
+ code,
52
+ client_id: config.clientId,
53
+ client_secret: config.clientSecret,
54
+ redirect_uri: redirectUri,
55
+ code_verifier: codeVerifier,
56
+ }),
57
+ });
58
+ if (!res.ok) {
59
+ throw new Error(`GitHub token exchange failed (${res.status}): ${await res.text()}`);
60
+ }
61
+ const body = (await res.json());
62
+ if (body.error) {
63
+ throw new Error(`GitHub token exchange failed: ${body.error_description ?? body.error}`);
64
+ }
65
+ if (!body.access_token) {
66
+ throw new Error('GitHub token exchange returned no access_token');
67
+ }
68
+ return {
69
+ accessToken: body.access_token,
70
+ ...(body.refresh_token !== undefined ? { refreshToken: body.refresh_token } : {}),
71
+ ...(body.expires_in !== undefined ? { expiresIn: body.expires_in } : {}),
72
+ };
73
+ },
74
+ getUserInfo: async (accessToken) => {
75
+ // 1. Profile
76
+ const profileRes = await fetch(`${apiBase}/user`, {
77
+ headers: {
78
+ Authorization: `Bearer ${accessToken}`,
79
+ Accept: 'application/vnd.github+json',
80
+ 'X-GitHub-Api-Version': '2022-11-28',
81
+ },
82
+ });
83
+ if (!profileRes.ok) {
84
+ throw new Error(`GitHub /user failed (${profileRes.status}): ${await profileRes.text()}`);
85
+ }
86
+ const profile = (await profileRes.json());
87
+ // 2. Verified primary email — GitHub returns null on /user.email when the
88
+ // user has hidden their email, so we always fetch the email list.
89
+ const emailRes = await fetch(`${apiBase}/user/emails`, {
90
+ headers: {
91
+ Authorization: `Bearer ${accessToken}`,
92
+ Accept: 'application/vnd.github+json',
93
+ 'X-GitHub-Api-Version': '2022-11-28',
94
+ },
95
+ });
96
+ if (!emailRes.ok) {
97
+ throw new Error(`GitHub /user/emails failed (${emailRes.status}): ${await emailRes.text()}`);
98
+ }
99
+ const emails = (await emailRes.json());
100
+ const primary = emails.find((e) => e.primary && e.verified) ?? emails.find((e) => e.verified);
101
+ if (!primary) {
102
+ throw new Error('GitHub account has no verified email');
103
+ }
104
+ return {
105
+ id: String(profile.id),
106
+ email: primary.email,
107
+ emailVerified: primary.verified,
108
+ ...(profile.name ? { name: profile.name } : { name: profile.login }),
109
+ ...(profile.avatar_url ? { picture: profile.avatar_url } : {}),
110
+ };
111
+ },
112
+ };
113
+ }
114
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/oauth/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { OAuthProvider } from '@authcore/types';
2
+ export interface GoogleProviderConfig {
3
+ clientId: string;
4
+ clientSecret: string;
5
+ /** Defaults to `['openid', 'email', 'profile']` — matches the OpenID Connect minimum. */
6
+ scopes?: string[];
7
+ }
8
+ /**
9
+ * Create a Google OAuth 2.0 / OpenID Connect provider.
10
+ *
11
+ * ```ts
12
+ * import { createGoogleProvider } from '@authcore/core'
13
+ * const google = createGoogleProvider({
14
+ * clientId: process.env.GOOGLE_CLIENT_ID!,
15
+ * clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
16
+ * })
17
+ * createAuth({ ..., oauth: { google } })
18
+ * ```
19
+ */
20
+ export declare function createGoogleProvider(config: GoogleProviderConfig): OAuthProvider;
21
+ //# sourceMappingURL=google.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/oauth/google.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,yFAAyF;IACzF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,aAAa,CA2EhF"}
@@ -0,0 +1,76 @@
1
+ const DEFAULT_SCOPES = ['openid', 'email', 'profile'];
2
+ /**
3
+ * Create a Google OAuth 2.0 / OpenID Connect provider.
4
+ *
5
+ * ```ts
6
+ * import { createGoogleProvider } from '@authcore/core'
7
+ * const google = createGoogleProvider({
8
+ * clientId: process.env.GOOGLE_CLIENT_ID!,
9
+ * clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
10
+ * })
11
+ * createAuth({ ..., oauth: { google } })
12
+ * ```
13
+ */
14
+ export function createGoogleProvider(config) {
15
+ const scopes = config.scopes ?? DEFAULT_SCOPES;
16
+ return {
17
+ id: 'google',
18
+ scopes,
19
+ authorize: ({ state, codeChallenge, redirectUri }) => {
20
+ const url = new URL('https://accounts.google.com/o/oauth2/v2/auth');
21
+ url.searchParams.set('client_id', config.clientId);
22
+ url.searchParams.set('redirect_uri', redirectUri);
23
+ url.searchParams.set('response_type', 'code');
24
+ url.searchParams.set('scope', scopes.join(' '));
25
+ url.searchParams.set('state', state);
26
+ url.searchParams.set('code_challenge', codeChallenge);
27
+ url.searchParams.set('code_challenge_method', 'S256');
28
+ // access_type=offline + prompt=consent ensures we get a refresh_token from Google.
29
+ // Without these, Google only issues a refresh token on the user's FIRST consent.
30
+ url.searchParams.set('access_type', 'offline');
31
+ url.searchParams.set('prompt', 'consent');
32
+ return url.toString();
33
+ },
34
+ exchangeCode: async ({ code, codeVerifier, redirectUri }) => {
35
+ const res = 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: config.clientId,
41
+ client_secret: config.clientSecret,
42
+ redirect_uri: redirectUri,
43
+ grant_type: 'authorization_code',
44
+ code_verifier: codeVerifier,
45
+ }),
46
+ });
47
+ if (!res.ok) {
48
+ throw new Error(`Google token exchange failed (${res.status}): ${await res.text()}`);
49
+ }
50
+ const body = (await res.json());
51
+ return {
52
+ accessToken: body.access_token,
53
+ ...(body.refresh_token !== undefined ? { refreshToken: body.refresh_token } : {}),
54
+ ...(body.expires_in !== undefined ? { expiresIn: body.expires_in } : {}),
55
+ ...(body.id_token !== undefined ? { idToken: body.id_token } : {}),
56
+ };
57
+ },
58
+ getUserInfo: async (accessToken) => {
59
+ const res = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
60
+ headers: { Authorization: `Bearer ${accessToken}` },
61
+ });
62
+ if (!res.ok) {
63
+ throw new Error(`Google userinfo failed (${res.status}): ${await res.text()}`);
64
+ }
65
+ const body = (await res.json());
66
+ return {
67
+ id: body.sub,
68
+ email: body.email,
69
+ emailVerified: body.email_verified === true,
70
+ ...(body.name !== undefined ? { name: body.name } : {}),
71
+ ...(body.picture !== undefined ? { picture: body.picture } : {}),
72
+ };
73
+ },
74
+ };
75
+ }
76
+ //# sourceMappingURL=google.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/oauth/google.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { OAuthProvider } from '@authcore/types';
2
+ export interface MicrosoftProviderConfig {
3
+ clientId: string;
4
+ clientSecret: string;
5
+ /**
6
+ * Defaults to `['openid', 'profile', 'email']`. Add `offline_access` to receive
7
+ * a refresh token. Add `User.Read` for Microsoft Graph profile reads.
8
+ */
9
+ scopes?: string[];
10
+ /**
11
+ * Microsoft tenant — controls which accounts can sign in.
12
+ * - `'common'` (default): personal Microsoft accounts AND work/school accounts in any Entra tenant.
13
+ * - `'organizations'`: any Entra (work/school) tenant, no personal accounts.
14
+ * - `'consumers'`: personal Microsoft accounts only.
15
+ * - `<tenant-id-or-domain>`: restrict to a specific tenant.
16
+ */
17
+ tenant?: 'common' | 'organizations' | 'consumers' | (string & {});
18
+ }
19
+ /**
20
+ * Create a Microsoft Identity Platform (Entra ID) OAuth 2.0 provider.
21
+ *
22
+ * ```ts
23
+ * import { createMicrosoftProvider } from '@authcore/core'
24
+ * const microsoft = createMicrosoftProvider({
25
+ * clientId: process.env.MICROSOFT_CLIENT_ID!,
26
+ * clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
27
+ * })
28
+ * createAuth({ ..., oauth: { microsoft } })
29
+ * ```
30
+ *
31
+ * Notes:
32
+ * - Reads the user's email + name from the OpenID Connect `id_token` claims
33
+ * (no extra Graph call needed). Falls back to `/me` on Microsoft Graph if
34
+ * the id_token is missing the email.
35
+ * - Microsoft does not have a `email_verified` flag in the id_token, so we
36
+ * treat the email as verified (Microsoft verifies emails before issuing
37
+ * accounts) — this matches the practice of other providers.
38
+ */
39
+ export declare function createMicrosoftProvider(config: MicrosoftProviderConfig): OAuthProvider;
40
+ //# sourceMappingURL=microsoft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"microsoft.d.ts","sourceRoot":"","sources":["../../src/oauth/microsoft.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,eAAe,GAAG,WAAW,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;CAClE;AAID;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,uBAAuB,GAAG,aAAa,CAmGtF"}
@@ -0,0 +1,126 @@
1
+ const DEFAULT_SCOPES = ['openid', 'profile', 'email'];
2
+ /**
3
+ * Create a Microsoft Identity Platform (Entra ID) OAuth 2.0 provider.
4
+ *
5
+ * ```ts
6
+ * import { createMicrosoftProvider } from '@authcore/core'
7
+ * const microsoft = createMicrosoftProvider({
8
+ * clientId: process.env.MICROSOFT_CLIENT_ID!,
9
+ * clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
10
+ * })
11
+ * createAuth({ ..., oauth: { microsoft } })
12
+ * ```
13
+ *
14
+ * Notes:
15
+ * - Reads the user's email + name from the OpenID Connect `id_token` claims
16
+ * (no extra Graph call needed). Falls back to `/me` on Microsoft Graph if
17
+ * the id_token is missing the email.
18
+ * - Microsoft does not have a `email_verified` flag in the id_token, so we
19
+ * treat the email as verified (Microsoft verifies emails before issuing
20
+ * accounts) — this matches the practice of other providers.
21
+ */
22
+ export function createMicrosoftProvider(config) {
23
+ const scopes = config.scopes ?? DEFAULT_SCOPES;
24
+ const tenant = config.tenant ?? 'common';
25
+ const base = `https://login.microsoftonline.com/${encodeURIComponent(tenant)}/oauth2/v2.0`;
26
+ return {
27
+ id: 'microsoft',
28
+ scopes,
29
+ authorize: ({ state, codeChallenge, redirectUri }) => {
30
+ const url = new URL(`${base}/authorize`);
31
+ url.searchParams.set('client_id', config.clientId);
32
+ url.searchParams.set('redirect_uri', redirectUri);
33
+ url.searchParams.set('response_type', 'code');
34
+ url.searchParams.set('response_mode', 'query');
35
+ url.searchParams.set('scope', scopes.join(' '));
36
+ url.searchParams.set('state', state);
37
+ url.searchParams.set('code_challenge', codeChallenge);
38
+ url.searchParams.set('code_challenge_method', 'S256');
39
+ return url.toString();
40
+ },
41
+ exchangeCode: async ({ code, codeVerifier, redirectUri }) => {
42
+ const res = await fetch(`${base}/token`, {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
45
+ body: new URLSearchParams({
46
+ code,
47
+ client_id: config.clientId,
48
+ client_secret: config.clientSecret,
49
+ redirect_uri: redirectUri,
50
+ grant_type: 'authorization_code',
51
+ code_verifier: codeVerifier,
52
+ scope: scopes.join(' '),
53
+ }),
54
+ });
55
+ if (!res.ok) {
56
+ throw new Error(`Microsoft token exchange failed (${res.status}): ${await res.text()}`);
57
+ }
58
+ const body = (await res.json());
59
+ return {
60
+ accessToken: body.access_token,
61
+ ...(body.refresh_token !== undefined ? { refreshToken: body.refresh_token } : {}),
62
+ ...(body.expires_in !== undefined ? { expiresIn: body.expires_in } : {}),
63
+ ...(body.id_token !== undefined ? { idToken: body.id_token } : {}),
64
+ };
65
+ },
66
+ getUserInfo: async (accessToken, idToken) => {
67
+ // Prefer id_token claims (no extra network call).
68
+ if (idToken) {
69
+ const claims = decodeIdTokenClaims(idToken);
70
+ if (claims && typeof claims['sub'] === 'string') {
71
+ const email = (typeof claims['email'] === 'string' && claims['email']) ||
72
+ (typeof claims['preferred_username'] === 'string' && claims['preferred_username']) ||
73
+ null;
74
+ if (email) {
75
+ return {
76
+ id: claims['sub'],
77
+ email,
78
+ emailVerified: true,
79
+ ...(typeof claims['name'] === 'string' ? { name: claims['name'] } : {}),
80
+ };
81
+ }
82
+ }
83
+ }
84
+ // Fallback to Microsoft Graph /me.
85
+ const res = await fetch('https://graph.microsoft.com/v1.0/me', {
86
+ headers: { Authorization: `Bearer ${accessToken}` },
87
+ });
88
+ if (!res.ok) {
89
+ throw new Error(`Microsoft Graph /me failed (${res.status}): ${await res.text()}`);
90
+ }
91
+ const me = (await res.json());
92
+ const email = me.mail ?? me.userPrincipalName;
93
+ if (!email) {
94
+ throw new Error('Microsoft account has no email or userPrincipalName');
95
+ }
96
+ return {
97
+ id: me.id,
98
+ email,
99
+ emailVerified: true,
100
+ ...(me.displayName ? { name: me.displayName } : {}),
101
+ };
102
+ },
103
+ };
104
+ }
105
+ /**
106
+ * Decode the *payload* of a JWS (id_token) without verifying its signature.
107
+ *
108
+ * Safe in this context because:
109
+ * - The id_token was just returned from a TLS-protected exchange with Microsoft.
110
+ * - We only use it to read non-sensitive identity claims (sub, email, name).
111
+ * - We do NOT use it for authentication decisions — AuthCore mints its own
112
+ * JWT (HS256, signed with `session.secret`) for the session.
113
+ */
114
+ function decodeIdTokenClaims(idToken) {
115
+ const parts = idToken.split('.');
116
+ if (parts.length < 2)
117
+ return null;
118
+ try {
119
+ const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
120
+ return JSON.parse(payload);
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ }
126
+ //# sourceMappingURL=microsoft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"microsoft.js","sourceRoot":"","sources":["../../src/oauth/microsoft.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,23 @@
3
3
  * Returns a 64-character hex string (256 bits of entropy).
4
4
  */
5
5
  export declare function generateOpaqueToken(): string;
6
+ /**
7
+ * Generate a CSRF token. Same shape as `generateOpaqueToken` (256 bits of entropy)
8
+ * but kept as a separate export to make intent explicit at call sites.
9
+ * The CSRF token is NOT hashed before storage — it's sent to the client as a cookie
10
+ * value AND compared byte-for-byte against the `X-CSRF-Token` header on each request.
11
+ */
12
+ export declare function generateCsrfToken(): string;
13
+ /**
14
+ * Generate a PKCE code verifier (RFC 7636 §4.1).
15
+ * 32 random bytes → 43-char base64url string (no padding), within the 43-128 char range.
16
+ */
17
+ export declare function generatePkceVerifier(): string;
18
+ /**
19
+ * Build the PKCE code challenge (S256 method) from a verifier.
20
+ * SHA-256 of the verifier, base64url-encoded (no padding).
21
+ */
22
+ export declare function pkceChallenge(verifier: string): string;
6
23
  /**
7
24
  * Hash a raw token using SHA-256 for safe database storage.
8
25
  * Store the hash; return the raw token to the user.
@@ -42,4 +59,24 @@ export declare function signJwt(payload: Omit<JwtPayload, 'iat' | 'exp'>, secret
42
59
  * @returns Decoded payload, or null if invalid/expired
43
60
  */
44
61
  export declare function verifyJwt(token: string, secret: string): JwtPayload | null;
62
+ /** Payload shape for a short-lived 2FA challenge token. */
63
+ export interface TwoFactorChallengePayload {
64
+ sub: string;
65
+ scope: '2fa-pending';
66
+ iat?: number;
67
+ exp?: number;
68
+ }
69
+ /**
70
+ * Sign a short-lived JWT used as a 2FA login challenge. The token carries the
71
+ * user id + a scope claim, and expires after `expiresIn` (default 5 minutes).
72
+ * It is NOT a session token — verify with {@link verifyTwoFactorChallenge}.
73
+ */
74
+ export declare function signTwoFactorChallenge(userId: string, secret: string, expiresIn?: string): string;
75
+ /**
76
+ * Verify a 2FA challenge token. Rejects tokens with the wrong scope so a
77
+ * session JWT can't be reused as a challenge (and vice versa).
78
+ *
79
+ * @returns The decoded payload, or `null` if invalid / expired / wrong scope.
80
+ */
81
+ export declare function verifyTwoFactorChallenge(token: string, secret: string): TwoFactorChallengePayload | null;
45
82
  //# sourceMappingURL=token.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/utils/token.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAK/D;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG,KAAK,CAAC,EACxC,MAAM,EAAE,MAAM,EACd,SAAS,SAAO,GACf,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAQ1E"}
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/utils/token.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAK/D;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG,KAAK,CAAC,EACxC,MAAM,EAAE,MAAM,EACd,SAAS,SAAO,GACf,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAQ1E;AAED,2DAA2D;AAC3D,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,aAAa,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,MAAM,CAI/F;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,yBAAyB,GAAG,IAAI,CAUlC"}