@draftlab/auth 0.2.5 → 0.3.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.
@@ -0,0 +1,140 @@
1
+ import { Provider } from "./provider.js";
2
+ import { Oauth2UserData, Oauth2WrappedConfig } from "./oauth2.js";
3
+
4
+ //#region src/provider/discord.d.ts
5
+
6
+ /**
7
+ * Configuration options for Discord OAuth 2.0 provider.
8
+ * Extends the base OAuth 2.0 configuration with Discord-specific documentation.
9
+ */
10
+ interface DiscordConfig extends Oauth2WrappedConfig {
11
+ /**
12
+ * Discord OAuth 2.0 client ID from Discord Developer Portal.
13
+ * Found in your Discord application settings.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * {
18
+ * clientID: "1234567890123456789"
19
+ * }
20
+ * ```
21
+ */
22
+ readonly clientID: string;
23
+ /**
24
+ * Discord OAuth 2.0 client secret from Discord Developer Portal.
25
+ * Keep this secure and never expose it to client-side code.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * {
30
+ * clientSecret: process.env.DISCORD_CLIENT_SECRET
31
+ * }
32
+ * ```
33
+ */
34
+ readonly clientSecret: string;
35
+ /**
36
+ * Discord OAuth scopes to request access for.
37
+ * Determines what data and actions your app can access.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * {
42
+ * scopes: [
43
+ * "identify", // Basic user information
44
+ * "email", // Email address
45
+ * "guilds", // User's Discord servers
46
+ * "connections" // Connected accounts (Steam, etc.)
47
+ * ]
48
+ * }
49
+ * ```
50
+ */
51
+ readonly scopes: string[];
52
+ /**
53
+ * Additional query parameters for Discord OAuth authorization.
54
+ * Useful for Discord-specific options like permissions.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * {
59
+ * query: {
60
+ * permissions: "8", // Administrator permission
61
+ * guild_id: "123456789", // Pre-select specific guild
62
+ * disable_guild_select: "true" // Disable guild selection
63
+ * }
64
+ * }
65
+ * ```
66
+ */
67
+ readonly query?: Record<string, string>;
68
+ }
69
+ /**
70
+ * Creates a Discord OAuth 2.0 authentication provider.
71
+ * Use this when you need access tokens to call Discord APIs on behalf of the user.
72
+ *
73
+ * @param config - Discord OAuth 2.0 configuration
74
+ * @returns OAuth 2.0 provider configured for Discord
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * // Basic Discord authentication
79
+ * const basicDiscord = DiscordProvider({
80
+ * clientID: process.env.DISCORD_CLIENT_ID,
81
+ * clientSecret: process.env.DISCORD_CLIENT_SECRET
82
+ * })
83
+ *
84
+ * // Discord with specific scopes
85
+ * const discordWithScopes = DiscordProvider({
86
+ * clientID: process.env.DISCORD_CLIENT_ID,
87
+ * clientSecret: process.env.DISCORD_CLIENT_SECRET,
88
+ * scopes: [
89
+ * "identify",
90
+ * "email",
91
+ * "guilds",
92
+ * "connections"
93
+ * ]
94
+ * })
95
+ *
96
+ * // Discord bot integration
97
+ * const discordBot = DiscordProvider({
98
+ * clientID: process.env.DISCORD_CLIENT_ID,
99
+ * clientSecret: process.env.DISCORD_CLIENT_SECRET,
100
+ * scopes: ["bot", "guilds"],
101
+ * query: {
102
+ * permissions: "2048" // Send Messages permission
103
+ * }
104
+ * })
105
+ *
106
+ * // Using the access token to fetch data
107
+ * export default issuer({
108
+ * providers: { discord: discordWithScopes },
109
+ * success: async (ctx, value) => {
110
+ * if (value.provider === "discord") {
111
+ * const token = value.tokenset.access
112
+ *
113
+ * // Get user profile
114
+ * const userRes = await fetch('https://discord.com/api/users/@me', {
115
+ * headers: { Authorization: `Bearer ${token}` }
116
+ * })
117
+ * const user = await userRes.json()
118
+ *
119
+ * // Get user guilds (if guilds scope granted)
120
+ * const guildsRes = await fetch('https://discord.com/api/users/@me/guilds', {
121
+ * headers: { Authorization: `Bearer ${token}` }
122
+ * })
123
+ * const guilds = await guildsRes.json()
124
+ *
125
+ * return ctx.subject("user", {
126
+ * discordId: user.id,
127
+ * username: user.username,
128
+ * discriminator: user.discriminator,
129
+ * email: user.email,
130
+ * avatar: user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png` : null,
131
+ * guildCount: guilds.length
132
+ * })
133
+ * }
134
+ * }
135
+ * })
136
+ * ```
137
+ */
138
+ declare const DiscordProvider: (config: DiscordConfig) => Provider<Oauth2UserData>;
139
+ //#endregion
140
+ export { DiscordConfig, DiscordProvider };
@@ -0,0 +1,85 @@
1
+ import { Oauth2Provider } from "./oauth2.js";
2
+
3
+ //#region src/provider/discord.ts
4
+ /**
5
+ * Creates a Discord OAuth 2.0 authentication provider.
6
+ * Use this when you need access tokens to call Discord APIs on behalf of the user.
7
+ *
8
+ * @param config - Discord OAuth 2.0 configuration
9
+ * @returns OAuth 2.0 provider configured for Discord
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // Basic Discord authentication
14
+ * const basicDiscord = DiscordProvider({
15
+ * clientID: process.env.DISCORD_CLIENT_ID,
16
+ * clientSecret: process.env.DISCORD_CLIENT_SECRET
17
+ * })
18
+ *
19
+ * // Discord with specific scopes
20
+ * const discordWithScopes = DiscordProvider({
21
+ * clientID: process.env.DISCORD_CLIENT_ID,
22
+ * clientSecret: process.env.DISCORD_CLIENT_SECRET,
23
+ * scopes: [
24
+ * "identify",
25
+ * "email",
26
+ * "guilds",
27
+ * "connections"
28
+ * ]
29
+ * })
30
+ *
31
+ * // Discord bot integration
32
+ * const discordBot = DiscordProvider({
33
+ * clientID: process.env.DISCORD_CLIENT_ID,
34
+ * clientSecret: process.env.DISCORD_CLIENT_SECRET,
35
+ * scopes: ["bot", "guilds"],
36
+ * query: {
37
+ * permissions: "2048" // Send Messages permission
38
+ * }
39
+ * })
40
+ *
41
+ * // Using the access token to fetch data
42
+ * export default issuer({
43
+ * providers: { discord: discordWithScopes },
44
+ * success: async (ctx, value) => {
45
+ * if (value.provider === "discord") {
46
+ * const token = value.tokenset.access
47
+ *
48
+ * // Get user profile
49
+ * const userRes = await fetch('https://discord.com/api/users/@me', {
50
+ * headers: { Authorization: `Bearer ${token}` }
51
+ * })
52
+ * const user = await userRes.json()
53
+ *
54
+ * // Get user guilds (if guilds scope granted)
55
+ * const guildsRes = await fetch('https://discord.com/api/users/@me/guilds', {
56
+ * headers: { Authorization: `Bearer ${token}` }
57
+ * })
58
+ * const guilds = await guildsRes.json()
59
+ *
60
+ * return ctx.subject("user", {
61
+ * discordId: user.id,
62
+ * username: user.username,
63
+ * discriminator: user.discriminator,
64
+ * email: user.email,
65
+ * avatar: user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png` : null,
66
+ * guildCount: guilds.length
67
+ * })
68
+ * }
69
+ * }
70
+ * })
71
+ * ```
72
+ */
73
+ const DiscordProvider = (config) => {
74
+ return Oauth2Provider({
75
+ ...config,
76
+ type: "discord",
77
+ endpoint: {
78
+ authorization: "https://discord.com/oauth2/authorize",
79
+ token: "https://discord.com/api/oauth2/token"
80
+ }
81
+ });
82
+ };
83
+
84
+ //#endregion
85
+ export { DiscordProvider };
@@ -0,0 +1,126 @@
1
+ import { Provider } from "./provider.js";
2
+ import { Oauth2UserData, Oauth2WrappedConfig } from "./oauth2.js";
3
+
4
+ //#region src/provider/linkedin.d.ts
5
+
6
+ /**
7
+ * Configuration options for LinkedIn OAuth 2.0 provider.
8
+ * Extends the base OAuth 2.0 configuration with LinkedIn-specific documentation.
9
+ */
10
+ interface LinkedInConfig extends Oauth2WrappedConfig {
11
+ /**
12
+ * LinkedIn OAuth 2.0 client ID from LinkedIn Developer Console.
13
+ * Found in your LinkedIn app settings.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * {
18
+ * clientID: "78abc123456789"
19
+ * }
20
+ * ```
21
+ */
22
+ readonly clientID: string;
23
+ /**
24
+ * LinkedIn OAuth 2.0 client secret from LinkedIn Developer Console.
25
+ * Keep this secure and never expose it to client-side code.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * {
30
+ * clientSecret: process.env.LINKEDIN_CLIENT_SECRET
31
+ * }
32
+ * ```
33
+ */
34
+ readonly clientSecret: string;
35
+ /**
36
+ * LinkedIn OAuth scopes to request access for.
37
+ * Determines what data and actions your app can access.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * {
42
+ * scopes: [
43
+ * "r_liteprofile", // Basic profile information
44
+ * "r_emailaddress", // Email address
45
+ * "w_member_social", // Share content on behalf of user
46
+ * "r_organization_social" // Organization content access
47
+ * ]
48
+ * }
49
+ * ```
50
+ */
51
+ readonly scopes: string[];
52
+ /**
53
+ * Additional query parameters for LinkedIn OAuth authorization.
54
+ * Useful for LinkedIn-specific options.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * {
59
+ * query: {
60
+ * state: "custom-state-value" // Custom state parameter
61
+ * }
62
+ * }
63
+ * ```
64
+ */
65
+ readonly query?: Record<string, string>;
66
+ }
67
+ /**
68
+ * Creates a LinkedIn OAuth 2.0 authentication provider.
69
+ * Use this when you need access tokens to call LinkedIn APIs on behalf of the user.
70
+ *
71
+ * @param config - LinkedIn OAuth 2.0 configuration
72
+ * @returns OAuth 2.0 provider configured for LinkedIn
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * // Basic LinkedIn authentication
77
+ * const basicLinkedIn = LinkedInProvider({
78
+ * clientID: process.env.LINKEDIN_CLIENT_ID,
79
+ * clientSecret: process.env.LINKEDIN_CLIENT_SECRET
80
+ * })
81
+ *
82
+ * // LinkedIn with specific scopes
83
+ * const linkedInWithScopes = LinkedInProvider({
84
+ * clientID: process.env.LINKEDIN_CLIENT_ID,
85
+ * clientSecret: process.env.LINKEDIN_CLIENT_SECRET,
86
+ * scopes: [
87
+ * "r_liteprofile",
88
+ * "r_emailaddress",
89
+ * "w_member_social"
90
+ * ]
91
+ * })
92
+ *
93
+ * // Using the access token to fetch data
94
+ * export default issuer({
95
+ * providers: { linkedin: linkedInWithScopes },
96
+ * success: async (ctx, value) => {
97
+ * if (value.provider === "linkedin") {
98
+ * const token = value.tokenset.access
99
+ *
100
+ * // Get user profile
101
+ * const profileRes = await fetch('https://api.linkedin.com/v2/people/~', {
102
+ * headers: { Authorization: `Bearer ${token}` }
103
+ * })
104
+ * const profile = await profileRes.json()
105
+ *
106
+ * // Get user email
107
+ * const emailRes = await fetch('https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))', {
108
+ * headers: { Authorization: `Bearer ${token}` }
109
+ * })
110
+ * const emailData = await emailRes.json()
111
+ *
112
+ * return ctx.subject("user", {
113
+ * linkedinId: profile.id,
114
+ * firstName: profile.localizedFirstName,
115
+ * lastName: profile.localizedLastName,
116
+ * email: emailData.elements[0]['handle~'].emailAddress,
117
+ * profileUrl: `https://www.linkedin.com/in/${profile.vanityName || profile.id}`
118
+ * })
119
+ * }
120
+ * }
121
+ * })
122
+ * ```
123
+ */
124
+ declare const LinkedInProvider: (config: LinkedInConfig) => Provider<Oauth2UserData>;
125
+ //#endregion
126
+ export { LinkedInConfig, LinkedInProvider };
@@ -0,0 +1,73 @@
1
+ import { Oauth2Provider } from "./oauth2.js";
2
+
3
+ //#region src/provider/linkedin.ts
4
+ /**
5
+ * Creates a LinkedIn OAuth 2.0 authentication provider.
6
+ * Use this when you need access tokens to call LinkedIn APIs on behalf of the user.
7
+ *
8
+ * @param config - LinkedIn OAuth 2.0 configuration
9
+ * @returns OAuth 2.0 provider configured for LinkedIn
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // Basic LinkedIn authentication
14
+ * const basicLinkedIn = LinkedInProvider({
15
+ * clientID: process.env.LINKEDIN_CLIENT_ID,
16
+ * clientSecret: process.env.LINKEDIN_CLIENT_SECRET
17
+ * })
18
+ *
19
+ * // LinkedIn with specific scopes
20
+ * const linkedInWithScopes = LinkedInProvider({
21
+ * clientID: process.env.LINKEDIN_CLIENT_ID,
22
+ * clientSecret: process.env.LINKEDIN_CLIENT_SECRET,
23
+ * scopes: [
24
+ * "r_liteprofile",
25
+ * "r_emailaddress",
26
+ * "w_member_social"
27
+ * ]
28
+ * })
29
+ *
30
+ * // Using the access token to fetch data
31
+ * export default issuer({
32
+ * providers: { linkedin: linkedInWithScopes },
33
+ * success: async (ctx, value) => {
34
+ * if (value.provider === "linkedin") {
35
+ * const token = value.tokenset.access
36
+ *
37
+ * // Get user profile
38
+ * const profileRes = await fetch('https://api.linkedin.com/v2/people/~', {
39
+ * headers: { Authorization: `Bearer ${token}` }
40
+ * })
41
+ * const profile = await profileRes.json()
42
+ *
43
+ * // Get user email
44
+ * const emailRes = await fetch('https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))', {
45
+ * headers: { Authorization: `Bearer ${token}` }
46
+ * })
47
+ * const emailData = await emailRes.json()
48
+ *
49
+ * return ctx.subject("user", {
50
+ * linkedinId: profile.id,
51
+ * firstName: profile.localizedFirstName,
52
+ * lastName: profile.localizedLastName,
53
+ * email: emailData.elements[0]['handle~'].emailAddress,
54
+ * profileUrl: `https://www.linkedin.com/in/${profile.vanityName || profile.id}`
55
+ * })
56
+ * }
57
+ * }
58
+ * })
59
+ * ```
60
+ */
61
+ const LinkedInProvider = (config) => {
62
+ return Oauth2Provider({
63
+ ...config,
64
+ type: "linkedin",
65
+ endpoint: {
66
+ authorization: "https://www.linkedin.com/oauth/v2/authorization",
67
+ token: "https://www.linkedin.com/oauth/v2/accessToken"
68
+ }
69
+ });
70
+ };
71
+
72
+ //#endregion
73
+ export { LinkedInProvider };
@@ -0,0 +1,172 @@
1
+ import { Provider } from "./provider.js";
2
+ import { Oauth2UserData, Oauth2WrappedConfig } from "./oauth2.js";
3
+
4
+ //#region src/provider/microsoft.d.ts
5
+
6
+ /**
7
+ * Configuration options for Microsoft OAuth 2.0 provider.
8
+ * Extends the base OAuth 2.0 configuration with Microsoft-specific documentation.
9
+ */
10
+ interface MicrosoftConfig extends Oauth2WrappedConfig {
11
+ /**
12
+ * Microsoft Azure AD tenant ID or tenant type.
13
+ * Determines which types of accounts can sign in.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * {
18
+ * tenant: "common" // Personal + work/school accounts
19
+ * // or
20
+ * tenant: "organizations" // Work/school accounts only
21
+ * // or
22
+ * tenant: "consumers" // Personal accounts only
23
+ * // or
24
+ * tenant: "12345678-1234-1234-1234-123456789012" // Specific tenant
25
+ * }
26
+ * ```
27
+ */
28
+ readonly tenant: string;
29
+ /**
30
+ * Microsoft OAuth 2.0 client ID from Azure App Registration.
31
+ * Found in your Azure portal app registration.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * {
36
+ * clientID: "12345678-1234-1234-1234-123456789012"
37
+ * }
38
+ * ```
39
+ */
40
+ readonly clientID: string;
41
+ /**
42
+ * Microsoft OAuth 2.0 client secret from Azure App Registration.
43
+ * Keep this secure and never expose it to client-side code.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * {
48
+ * clientSecret: process.env.MICROSOFT_CLIENT_SECRET
49
+ * }
50
+ * ```
51
+ */
52
+ readonly clientSecret: string;
53
+ /**
54
+ * Microsoft OAuth scopes to request access for.
55
+ * Determines what data and actions your app can access via Microsoft Graph.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * {
60
+ * scopes: [
61
+ * "openid", // OpenID Connect sign-in
62
+ * "profile", // Basic profile
63
+ * "email", // Email address
64
+ * "User.Read", // Read user profile
65
+ * "Mail.Read", // Read user mail
66
+ * "Calendars.Read" // Read user calendars
67
+ * ]
68
+ * }
69
+ * ```
70
+ */
71
+ readonly scopes: string[];
72
+ /**
73
+ * Additional query parameters for Microsoft OAuth authorization.
74
+ * Useful for Microsoft-specific options like domain hints.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * {
79
+ * query: {
80
+ * domain_hint: "contoso.com", // Pre-fill domain
81
+ * login_hint: "user@contoso.com", // Pre-fill username
82
+ * prompt: "consent" // Force consent screen
83
+ * }
84
+ * }
85
+ * ```
86
+ */
87
+ readonly query?: Record<string, string>;
88
+ }
89
+ /**
90
+ * Creates a Microsoft OAuth 2.0 authentication provider.
91
+ * Use this when you need access tokens to call Microsoft Graph APIs on behalf of the user.
92
+ *
93
+ * @param config - Microsoft OAuth 2.0 configuration
94
+ * @returns OAuth 2.0 provider configured for Microsoft
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * // Basic Microsoft authentication (all account types)
99
+ * const basicMicrosoft = MicrosoftProvider({
100
+ * tenant: "common",
101
+ * clientID: process.env.MICROSOFT_CLIENT_ID,
102
+ * clientSecret: process.env.MICROSOFT_CLIENT_SECRET
103
+ * })
104
+ *
105
+ * // Work/school accounts only
106
+ * const workMicrosoft = MicrosoftProvider({
107
+ * tenant: "organizations",
108
+ * clientID: process.env.MICROSOFT_CLIENT_ID,
109
+ * clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
110
+ * scopes: [
111
+ * "openid",
112
+ * "profile",
113
+ * "email",
114
+ * "User.Read",
115
+ * "Mail.Read"
116
+ * ]
117
+ * })
118
+ *
119
+ * // Specific tenant with advanced scopes
120
+ * const enterpriseMicrosoft = MicrosoftProvider({
121
+ * tenant: "12345678-1234-1234-1234-123456789012",
122
+ * clientID: process.env.MICROSOFT_CLIENT_ID,
123
+ * clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
124
+ * scopes: [
125
+ * "openid",
126
+ * "profile",
127
+ * "email",
128
+ * "User.Read",
129
+ * "Directory.Read.All",
130
+ * "Sites.Read.All"
131
+ * ],
132
+ * query: {
133
+ * domain_hint: "contoso.com"
134
+ * }
135
+ * })
136
+ *
137
+ * // Using the access token to fetch data
138
+ * export default issuer({
139
+ * providers: { microsoft: workMicrosoft },
140
+ * success: async (ctx, value) => {
141
+ * if (value.provider === "microsoft") {
142
+ * const token = value.tokenset.access
143
+ *
144
+ * // Get user profile from Microsoft Graph
145
+ * const userRes = await fetch('https://graph.microsoft.com/v1.0/me', {
146
+ * headers: { Authorization: `Bearer ${token}` }
147
+ * })
148
+ * const user = await userRes.json()
149
+ *
150
+ * // Get user's manager (if available)
151
+ * const managerRes = await fetch('https://graph.microsoft.com/v1.0/me/manager', {
152
+ * headers: { Authorization: `Bearer ${token}` }
153
+ * })
154
+ * const manager = await managerRes.json()
155
+ *
156
+ * return ctx.subject("user", {
157
+ * microsoftId: user.id,
158
+ * displayName: user.displayName,
159
+ * email: user.mail || user.userPrincipalName,
160
+ * jobTitle: user.jobTitle,
161
+ * department: user.department,
162
+ * officeLocation: user.officeLocation,
163
+ * managerName: manager?.displayName
164
+ * })
165
+ * }
166
+ * }
167
+ * })
168
+ * ```
169
+ */
170
+ declare const MicrosoftProvider: (config: MicrosoftConfig) => Provider<Oauth2UserData>;
171
+ //#endregion
172
+ export { MicrosoftConfig, MicrosoftProvider };
@@ -0,0 +1,97 @@
1
+ import { Oauth2Provider } from "./oauth2.js";
2
+
3
+ //#region src/provider/microsoft.ts
4
+ /**
5
+ * Creates a Microsoft OAuth 2.0 authentication provider.
6
+ * Use this when you need access tokens to call Microsoft Graph APIs on behalf of the user.
7
+ *
8
+ * @param config - Microsoft OAuth 2.0 configuration
9
+ * @returns OAuth 2.0 provider configured for Microsoft
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // Basic Microsoft authentication (all account types)
14
+ * const basicMicrosoft = MicrosoftProvider({
15
+ * tenant: "common",
16
+ * clientID: process.env.MICROSOFT_CLIENT_ID,
17
+ * clientSecret: process.env.MICROSOFT_CLIENT_SECRET
18
+ * })
19
+ *
20
+ * // Work/school accounts only
21
+ * const workMicrosoft = MicrosoftProvider({
22
+ * tenant: "organizations",
23
+ * clientID: process.env.MICROSOFT_CLIENT_ID,
24
+ * clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
25
+ * scopes: [
26
+ * "openid",
27
+ * "profile",
28
+ * "email",
29
+ * "User.Read",
30
+ * "Mail.Read"
31
+ * ]
32
+ * })
33
+ *
34
+ * // Specific tenant with advanced scopes
35
+ * const enterpriseMicrosoft = MicrosoftProvider({
36
+ * tenant: "12345678-1234-1234-1234-123456789012",
37
+ * clientID: process.env.MICROSOFT_CLIENT_ID,
38
+ * clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
39
+ * scopes: [
40
+ * "openid",
41
+ * "profile",
42
+ * "email",
43
+ * "User.Read",
44
+ * "Directory.Read.All",
45
+ * "Sites.Read.All"
46
+ * ],
47
+ * query: {
48
+ * domain_hint: "contoso.com"
49
+ * }
50
+ * })
51
+ *
52
+ * // Using the access token to fetch data
53
+ * export default issuer({
54
+ * providers: { microsoft: workMicrosoft },
55
+ * success: async (ctx, value) => {
56
+ * if (value.provider === "microsoft") {
57
+ * const token = value.tokenset.access
58
+ *
59
+ * // Get user profile from Microsoft Graph
60
+ * const userRes = await fetch('https://graph.microsoft.com/v1.0/me', {
61
+ * headers: { Authorization: `Bearer ${token}` }
62
+ * })
63
+ * const user = await userRes.json()
64
+ *
65
+ * // Get user's manager (if available)
66
+ * const managerRes = await fetch('https://graph.microsoft.com/v1.0/me/manager', {
67
+ * headers: { Authorization: `Bearer ${token}` }
68
+ * })
69
+ * const manager = await managerRes.json()
70
+ *
71
+ * return ctx.subject("user", {
72
+ * microsoftId: user.id,
73
+ * displayName: user.displayName,
74
+ * email: user.mail || user.userPrincipalName,
75
+ * jobTitle: user.jobTitle,
76
+ * department: user.department,
77
+ * officeLocation: user.officeLocation,
78
+ * managerName: manager?.displayName
79
+ * })
80
+ * }
81
+ * }
82
+ * })
83
+ * ```
84
+ */
85
+ const MicrosoftProvider = (config) => {
86
+ return Oauth2Provider({
87
+ ...config,
88
+ type: "microsoft",
89
+ endpoint: {
90
+ authorization: `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/authorize`,
91
+ token: `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/token`
92
+ }
93
+ });
94
+ };
95
+
96
+ //#endregion
97
+ export { MicrosoftProvider };
@@ -47,14 +47,6 @@ interface TOTPProviderConfig {
47
47
  * @param error - Optional error message to display
48
48
  */
49
49
  register: (req: Request, qrCodeUrl: string, secret: string, backupCodes: string[], error?: string, email?: string) => Promise<Response>;
50
- /**
51
- * Custom verification handler that generates the UI for TOTP verification.
52
- * Called when user needs to enter their TOTP code.
53
- *
54
- * @param req - The HTTP request object
55
- * @param error - Optional error message to display
56
- */
57
- verify: (req: Request, error?: string) => Promise<Response>;
58
50
  /**
59
51
  * Custom recovery handler that generates the UI for backup code entry.
60
52
  * Called when user wants to use a recovery code instead of TOTP.
@@ -42,9 +42,6 @@ const TOTPProvider = (config) => {
42
42
  const saveTOTPData = async (userId, data) => {
43
43
  await Storage.set(ctx.storage, totpKey(userId), data);
44
44
  };
45
- const deleteTOTPData = async (userId) => {
46
- await Storage.remove(ctx.storage, totpKey(userId));
47
- };
48
45
  const generateBackupCodes = (count) => {
49
46
  const codes = [];
50
47
  for (let i = 0; i < count; i++) {
@@ -66,7 +63,7 @@ const TOTPProvider = (config) => {
66
63
  routes.get("/register", async (c) => {
67
64
  return ctx.forward(c, await config.register(c.request, "", "", []));
68
65
  });
69
- routes.post("/register-verify", async (c) => {
66
+ routes.post("/register", async (c) => {
70
67
  const formData = await c.formData();
71
68
  const email = formData.get("email")?.toString();
72
69
  const action = formData.get("action")?.toString();
@@ -110,13 +107,13 @@ const TOTPProvider = (config) => {
110
107
  routes.get("/authorize", async (c) => {
111
108
  return ctx.forward(c, await config.authorize(c.request));
112
109
  });
113
- routes.post("/verify", async (c) => {
110
+ routes.post("/authorize", async (c) => {
114
111
  const formData = await c.formData();
115
112
  const email = formData.get("email")?.toString();
116
113
  const token = formData.get("token")?.toString();
117
- if (!email || !token) return ctx.forward(c, await config.verify(c.request, "Email and verification code are required"));
114
+ if (!email || !token) return ctx.forward(c, await config.authorize(c.request, "Email and verification code are required"));
118
115
  const totpData = await getTOTPData(email);
119
- if (!totpData || !totpData.enabled) return ctx.forward(c, await config.verify(c.request, "TOTP is not set up for this email"));
116
+ if (!totpData || !totpData.enabled) return ctx.forward(c, await config.authorize(c.request, "TOTP is not set up for this email"));
120
117
  const totp = createTOTPInstance(totpData.secret, totpData.label || email);
121
118
  const delta = totp.validate({
122
119
  token,
@@ -126,12 +123,12 @@ const TOTPProvider = (config) => {
126
123
  email,
127
124
  method: "totp"
128
125
  });
129
- return ctx.forward(c, await config.verify(c.request, "Invalid verification code"));
126
+ return ctx.forward(c, await config.authorize(c.request, "Invalid verification code"));
130
127
  });
131
128
  routes.get("/recovery", async (c) => {
132
129
  return ctx.forward(c, await config.recovery(c.request));
133
130
  });
134
- routes.post("/recovery-verify", async (c) => {
131
+ routes.post("/recovery", async (c) => {
135
132
  const formData = await c.formData();
136
133
  const email = formData.get("email")?.toString();
137
134
  const code = formData.get("code")?.toString()?.toUpperCase();
@@ -149,40 +146,6 @@ const TOTPProvider = (config) => {
149
146
  }
150
147
  return ctx.forward(c, await config.recovery(c.request, "Invalid or already used recovery code"));
151
148
  });
152
- routes.post("/disable", async (c) => {
153
- const userId = c.query("userId");
154
- if (!userId) return c.json({ error: "User ID is required" }, { status: 400 });
155
- await deleteTOTPData(userId);
156
- return c.json({
157
- success: true,
158
- message: "TOTP has been disabled"
159
- });
160
- });
161
- routes.get("/status", async (c) => {
162
- const userId = c.query("userId");
163
- if (!userId) return c.json({ error: "User ID is required" }, { status: 400 });
164
- const totpData = await getTOTPData(userId);
165
- return c.json({
166
- enabled: totpData?.enabled || false,
167
- hasBackupCodes: (totpData?.backupCodes?.length || 0) > 0,
168
- backupCodesCount: totpData?.backupCodes?.length || 0,
169
- setupDate: totpData?.createdAt
170
- });
171
- });
172
- routes.post("/regenerate-backup-codes", async (c) => {
173
- const userId = c.query("userId");
174
- if (!userId) return c.json({ error: "User ID is required" }, { status: 400 });
175
- const totpData = await getTOTPData(userId);
176
- if (!totpData || !totpData.enabled) return c.json({ error: "TOTP is not enabled for this user" }, { status: 400 });
177
- const newBackupCodes = generateBackupCodes(backupCodesCount);
178
- totpData.backupCodes = newBackupCodes;
179
- await saveTOTPData(userId, totpData);
180
- return c.json({
181
- success: true,
182
- backupCodes: newBackupCodes,
183
- message: "New backup codes generated"
184
- });
185
- });
186
149
  }
187
150
  };
188
151
  };
package/dist/ui/select.js CHANGED
@@ -8,16 +8,7 @@ import { jsx, jsxs } from "preact/jsx-runtime";
8
8
  */
9
9
  const PROVIDER_ICONS = {
10
10
  github: ICON_GITHUB,
11
- google: ICON_GOOGLE,
12
- code: () => /* @__PURE__ */ jsx("svg", {
13
- width: "20",
14
- height: "20",
15
- viewBox: "0 0 52 52",
16
- fill: "currentColor",
17
- "aria-label": "Code authentication",
18
- role: "img",
19
- children: /* @__PURE__ */ jsx("path", { d: "M8.55,36.91A6.55,6.55,0,1,1,2,43.45,6.54,6.54,0,0,1,8.55,36.91Zm17.45,0a6.55,6.55,0,1,1-6.55,6.54A6.55,6.55,0,0,1,26,36.91Zm17.45,0a6.55,6.55,0,1,1-6.54,6.54A6.54,6.54,0,0,1,43.45,36.91ZM8.55,19.45A6.55,6.55,0,1,1,2,26,6.55,6.55,0,0,1,8.55,19.45Zm17.45,0A6.55,6.55,0,1,1,19.45,26,6.56,6.56,0,0,1,26,19.45Zm17.45,0A6.55,6.55,0,1,1,36.91,26,6.55,6.55,0,0,1,43.45,19.45ZM8.55,2A6.55,6.55,0,1,1,2,8.55,6.54,6.54,0,0,1,8.55,2ZM26,2a6.55,6.55,0,1,1-6.55,6.55A6.55,6.55,0,0,1,26,2ZM43.45,2a6.55,6.55,0,1,1-6.54,6.55A6.55,6.55,0,0,1,43.45,2Z" })
20
- })
11
+ google: ICON_GOOGLE
21
12
  };
22
13
  /**
23
14
  * Default display names for provider types
@@ -30,28 +21,6 @@ const DEFAULT_DISPLAYS = {
30
21
  password: "Password"
31
22
  };
32
23
  /**
33
- * Individual provider button component
34
- */
35
- const ProviderButton = ({ providerKey, providerType, config, displays, buttonText }) => {
36
- const displayName = config?.display || displays[providerType] || DEFAULT_DISPLAYS[providerType] || providerType;
37
- const IconComponent = PROVIDER_ICONS[providerKey] || PROVIDER_ICONS[providerType];
38
- return /* @__PURE__ */ jsxs("a", {
39
- href: `./${providerKey}/authorize`,
40
- "data-component": "button",
41
- "data-color": "ghost",
42
- "aria-label": `${buttonText} ${displayName}`,
43
- children: [
44
- IconComponent && /* @__PURE__ */ jsx("i", {
45
- "data-slot": "icon",
46
- children: /* @__PURE__ */ jsx(IconComponent, {})
47
- }),
48
- buttonText,
49
- " ",
50
- displayName
51
- ]
52
- });
53
- };
54
- /**
55
24
  * Main provider selection component
56
25
  */
57
26
  const ProviderSelect = ({ providers, config = {}, theme }) => {
@@ -66,13 +35,25 @@ const ProviderSelect = ({ providers, config = {}, theme }) => {
66
35
  title: "Sign In",
67
36
  children: /* @__PURE__ */ jsx("div", {
68
37
  "data-component": "form",
69
- children: visibleProviders.map(([key, type]) => /* @__PURE__ */ jsx(ProviderButton, {
70
- providerKey: key,
71
- providerType: type,
72
- config: config.providers?.[key],
73
- displays,
74
- buttonText
75
- }, key))
38
+ children: visibleProviders.map(([key, type]) => {
39
+ const displayName = config.providers?.[key]?.display || displays[type] || DEFAULT_DISPLAYS[type] || type;
40
+ const IconComponent = PROVIDER_ICONS[key] || PROVIDER_ICONS[type];
41
+ return /* @__PURE__ */ jsxs("a", {
42
+ href: `./${key}/authorize`,
43
+ "data-component": "button",
44
+ "data-color": "ghost",
45
+ "aria-label": `${buttonText} ${displayName}`,
46
+ children: [
47
+ IconComponent && /* @__PURE__ */ jsx("i", {
48
+ "data-slot": "icon",
49
+ children: /* @__PURE__ */ jsx(IconComponent, {})
50
+ }),
51
+ buttonText,
52
+ " ",
53
+ displayName
54
+ ]
55
+ }, key);
56
+ })
76
57
  })
77
58
  });
78
59
  };
package/dist/ui/totp.d.ts CHANGED
@@ -6,12 +6,6 @@ import { TOTPProviderConfig } from "../provider/totp.js";
6
6
  * Strongly typed copy text configuration for TOTP UI
7
7
  */
8
8
  interface TOTPUICopy {
9
- readonly setup_title: string;
10
- readonly setup_description: string;
11
- readonly verify_title: string;
12
- readonly verify_description: string;
13
- readonly recovery_title: string;
14
- readonly recovery_description: string;
15
9
  readonly setup_manual_entry: string;
16
10
  readonly setup_backup_codes_title: string;
17
11
  readonly setup_backup_codes_description: string;
package/dist/ui/totp.js CHANGED
@@ -5,12 +5,6 @@ import QRCode from "qrcode";
5
5
 
6
6
  //#region src/ui/totp.tsx
7
7
  const DEFAULT_COPY = {
8
- setup_title: "Set up Two-Factor Authentication",
9
- setup_description: "Scan the QR code with your authenticator app, then enter the verification code.",
10
- verify_title: "Enter authentication code",
11
- verify_description: "Open your authenticator app and enter the current 6-digit code.",
12
- recovery_title: "Use backup code",
13
- recovery_description: "Enter one of your backup codes. Each code can only be used once.",
14
8
  setup_manual_entry: "Can't scan? Enter this code manually:",
15
9
  setup_backup_codes_title: "Save your backup codes",
16
10
  setup_backup_codes_description: "Store these backup codes in a safe place. You can use them to access your account if you lose your device.",
@@ -54,7 +48,7 @@ const TOTPUI = (options = {}) => {
54
48
  if (!qrCodeUrl) return /* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsxs("form", {
55
49
  "data-component": "form",
56
50
  method: "post",
57
- action: "./register-verify",
51
+ action: "./register",
58
52
  children: [
59
53
  /* @__PURE__ */ jsx(FormAlert, { message: error }),
60
54
  /* @__PURE__ */ jsx("input", {
@@ -93,7 +87,7 @@ const TOTPUI = (options = {}) => {
93
87
  return /* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsxs("form", {
94
88
  "data-component": "form",
95
89
  method: "post",
96
- action: "./register-verify",
90
+ action: "./register",
97
91
  children: [
98
92
  /* @__PURE__ */ jsx(FormAlert, { message: error }),
99
93
  email && /* @__PURE__ */ jsx("input", {
@@ -169,7 +163,7 @@ const TOTPUI = (options = {}) => {
169
163
  const renderAuthorize = (error) => /* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsxs("form", {
170
164
  "data-component": "form",
171
165
  method: "post",
172
- action: "./verify",
166
+ action: "./authorize",
173
167
  children: [
174
168
  /* @__PURE__ */ jsx(FormAlert, { message: error }),
175
169
  /* @__PURE__ */ jsx("input", {
@@ -215,55 +209,12 @@ const TOTPUI = (options = {}) => {
215
209
  ]
216
210
  }) });
217
211
  /**
218
- * Renders the verification form
219
- */
220
- const renderVerify = (error) => /* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsxs("form", {
221
- "data-component": "form",
222
- method: "post",
223
- action: "./verify",
224
- children: [
225
- /* @__PURE__ */ jsx(FormAlert, { message: error }),
226
- /* @__PURE__ */ jsx("input", {
227
- type: "email",
228
- name: "email",
229
- placeholder: "Email",
230
- autoComplete: "email",
231
- "data-component": "input",
232
- required: true
233
- }),
234
- /* @__PURE__ */ jsx("input", {
235
- type: "text",
236
- name: "token",
237
- placeholder: copy.input_token,
238
- pattern: "[0-9]{6}",
239
- maxLength: 6,
240
- minLength: 6,
241
- autoComplete: "one-time-code",
242
- "data-component": "input",
243
- required: true
244
- }),
245
- /* @__PURE__ */ jsx("button", {
246
- type: "submit",
247
- "data-component": "button",
248
- children: copy.button_continue
249
- }),
250
- /* @__PURE__ */ jsx("div", {
251
- "data-component": "form-footer",
252
- children: /* @__PURE__ */ jsx("a", {
253
- href: "./recovery",
254
- "data-component": "link",
255
- children: copy.link_use_recovery
256
- })
257
- })
258
- ]
259
- }) });
260
- /**
261
212
  * Renders the recovery form
262
213
  */
263
214
  const renderRecovery = (error) => /* @__PURE__ */ jsx(Layout, { children: /* @__PURE__ */ jsxs("form", {
264
215
  "data-component": "form",
265
216
  method: "post",
266
- action: "./recovery-verify",
217
+ action: "./recovery",
267
218
  children: [
268
219
  /* @__PURE__ */ jsx(FormAlert, { message: error }),
269
220
  /* @__PURE__ */ jsx("input", {
@@ -308,10 +259,6 @@ const TOTPUI = (options = {}) => {
308
259
  const jsx$1 = await renderRegister(qrCodeUrl, secret, backupCodes, error, email);
309
260
  return new Response(renderToHTML(jsx$1), { headers: { "Content-Type": "text/html" } });
310
261
  },
311
- verify: async (_req, error) => {
312
- const jsx$1 = renderVerify(error);
313
- return new Response(renderToHTML(jsx$1), { headers: { "Content-Type": "text/html" } });
314
- },
315
262
  recovery: async (_req, error) => {
316
263
  const jsx$1 = renderRecovery(error);
317
264
  return new Response(renderToHTML(jsx$1), { headers: { "Content-Type": "text/html" } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@draftlab/auth",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Core implementation for @draftlab/auth",
6
6
  "author": "Matheus Pergoli",
@@ -60,10 +60,10 @@
60
60
  "@standard-schema/spec": "^1.0.0",
61
61
  "jose": "^6.0.12",
62
62
  "otpauth": "^9.4.0",
63
- "preact": "^10.26.9",
63
+ "preact": "^10.27.0",
64
64
  "preact-render-to-string": "^6.5.13",
65
65
  "qrcode": "^1.5.4",
66
- "@draftlab/auth-router": "0.0.4"
66
+ "@draftlab/auth-router": "0.0.5"
67
67
  },
68
68
  "engines": {
69
69
  "node": ">=18"