@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.
- package/dist/provider/discord.d.ts +140 -0
- package/dist/provider/discord.js +85 -0
- package/dist/provider/linkedin.d.ts +126 -0
- package/dist/provider/linkedin.js +73 -0
- package/dist/provider/microsoft.d.ts +172 -0
- package/dist/provider/microsoft.js +97 -0
- package/dist/provider/totp.d.ts +0 -8
- package/dist/provider/totp.js +6 -43
- package/dist/ui/select.js +20 -39
- package/dist/ui/totp.d.ts +0 -6
- package/dist/ui/totp.js +4 -57
- package/package.json +3 -3
|
@@ -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 };
|
package/dist/provider/totp.d.ts
CHANGED
|
@@ -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.
|
package/dist/provider/totp.js
CHANGED
|
@@ -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
|
|
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("/
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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]) =>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
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: "./
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
66
|
+
"@draftlab/auth-router": "0.0.5"
|
|
67
67
|
},
|
|
68
68
|
"engines": {
|
|
69
69
|
"node": ">=18"
|