@ereo/auth 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -0
- package/dist/auth.d.ts +220 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1028 -0
- package/dist/providers/index.d.ts +191 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +495 -0
- package/package.json +47 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ereo/auth/providers - Built-in authentication providers
|
|
3
|
+
*
|
|
4
|
+
* Provides credential-based and OAuth authentication providers.
|
|
5
|
+
*/
|
|
6
|
+
import type { AuthProvider, User, Session } from '../auth';
|
|
7
|
+
/** Credentials provider configuration */
|
|
8
|
+
export interface CredentialsConfig {
|
|
9
|
+
/** Provider display name */
|
|
10
|
+
name?: string;
|
|
11
|
+
/** Custom provider ID (default: 'credentials') */
|
|
12
|
+
id?: string;
|
|
13
|
+
/** Authorize function - receives credentials, returns user or null */
|
|
14
|
+
authorize: (credentials: Record<string, unknown>) => Promise<User | null>;
|
|
15
|
+
}
|
|
16
|
+
/** OAuth provider base configuration */
|
|
17
|
+
export interface OAuthConfig {
|
|
18
|
+
/** OAuth client ID */
|
|
19
|
+
clientId: string;
|
|
20
|
+
/** OAuth client secret */
|
|
21
|
+
clientSecret: string;
|
|
22
|
+
/** Redirect URI (callback URL) */
|
|
23
|
+
redirectUri?: string;
|
|
24
|
+
/** OAuth scopes to request */
|
|
25
|
+
scope?: string[];
|
|
26
|
+
/** Authorization URL */
|
|
27
|
+
authorizationUrl?: string;
|
|
28
|
+
/** Token URL */
|
|
29
|
+
tokenUrl?: string;
|
|
30
|
+
/** User info URL */
|
|
31
|
+
userInfoUrl?: string;
|
|
32
|
+
}
|
|
33
|
+
/** GitHub OAuth configuration */
|
|
34
|
+
export interface GitHubConfig extends OAuthConfig {
|
|
35
|
+
/** Allow sign-in with GitHub Enterprise */
|
|
36
|
+
enterprise?: {
|
|
37
|
+
baseUrl: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Google OAuth configuration */
|
|
41
|
+
export interface GoogleConfig extends OAuthConfig {
|
|
42
|
+
/** Enable Google Workspace domain restriction */
|
|
43
|
+
hostedDomain?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Discord OAuth configuration */
|
|
46
|
+
export interface DiscordConfig extends OAuthConfig {
|
|
47
|
+
/** Discord guild ID to require membership */
|
|
48
|
+
guildId?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Generic OAuth provider configuration */
|
|
51
|
+
export interface GenericOAuthConfig extends OAuthConfig {
|
|
52
|
+
/** Provider ID */
|
|
53
|
+
id: string;
|
|
54
|
+
/** Provider display name */
|
|
55
|
+
name: string;
|
|
56
|
+
/** Profile URL for fetching user data */
|
|
57
|
+
profileUrl?: string;
|
|
58
|
+
/** Function to map provider profile to User */
|
|
59
|
+
profile?: (profile: Record<string, unknown>, tokens: OAuthTokens) => User | Promise<User>;
|
|
60
|
+
}
|
|
61
|
+
/** OAuth tokens returned from token exchange */
|
|
62
|
+
export interface OAuthTokens {
|
|
63
|
+
access_token: string;
|
|
64
|
+
token_type: string;
|
|
65
|
+
refresh_token?: string;
|
|
66
|
+
expires_in?: number;
|
|
67
|
+
scope?: string;
|
|
68
|
+
id_token?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Credentials provider (email/password or custom authentication).
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* credentials({
|
|
76
|
+
* authorize: async ({ email, password }) => {
|
|
77
|
+
* const user = await db.users.findByEmail(email);
|
|
78
|
+
* if (user && await bcrypt.compare(password, user.passwordHash)) {
|
|
79
|
+
* return { id: user.id, email: user.email, roles: user.roles };
|
|
80
|
+
* }
|
|
81
|
+
* return null;
|
|
82
|
+
* }
|
|
83
|
+
* })
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare function credentials(config: CredentialsConfig): AuthProvider;
|
|
87
|
+
/**
|
|
88
|
+
* GitHub OAuth provider.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* github({
|
|
93
|
+
* clientId: process.env.GITHUB_CLIENT_ID,
|
|
94
|
+
* clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
95
|
+
* })
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare function github(config: GitHubConfig): AuthProvider;
|
|
99
|
+
/**
|
|
100
|
+
* Google OAuth provider.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* google({
|
|
105
|
+
* clientId: process.env.GOOGLE_CLIENT_ID,
|
|
106
|
+
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
107
|
+
* })
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function google(config: GoogleConfig): AuthProvider;
|
|
111
|
+
/**
|
|
112
|
+
* Discord OAuth provider.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* discord({
|
|
117
|
+
* clientId: process.env.DISCORD_CLIENT_ID,
|
|
118
|
+
* clientSecret: process.env.DISCORD_CLIENT_SECRET,
|
|
119
|
+
* })
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function discord(config: DiscordConfig): AuthProvider;
|
|
123
|
+
/**
|
|
124
|
+
* Generic OAuth provider for any OAuth 2.0 service.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* oauth({
|
|
129
|
+
* id: 'custom',
|
|
130
|
+
* name: 'Custom OAuth',
|
|
131
|
+
* clientId: process.env.OAUTH_CLIENT_ID,
|
|
132
|
+
* clientSecret: process.env.OAUTH_CLIENT_SECRET,
|
|
133
|
+
* authorizationUrl: 'https://example.com/oauth/authorize',
|
|
134
|
+
* tokenUrl: 'https://example.com/oauth/token',
|
|
135
|
+
* userInfoUrl: 'https://example.com/api/user',
|
|
136
|
+
* profile: (profile) => ({
|
|
137
|
+
* id: profile.sub,
|
|
138
|
+
* email: profile.email,
|
|
139
|
+
* name: profile.name,
|
|
140
|
+
* }),
|
|
141
|
+
* })
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export declare function oauth(config: GenericOAuthConfig): AuthProvider;
|
|
145
|
+
/** Mock provider configuration */
|
|
146
|
+
export interface MockConfig {
|
|
147
|
+
/** Pre-configured session to return */
|
|
148
|
+
session?: Session;
|
|
149
|
+
/** Pre-configured user to return */
|
|
150
|
+
user?: User;
|
|
151
|
+
/** Delay before returning (for testing loading states) */
|
|
152
|
+
delay?: number;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Simple mock provider for development and testing.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* mock({
|
|
160
|
+
* user: { id: 'test-123', email: 'test@example.com', roles: ['admin'] }
|
|
161
|
+
* })
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export declare function mock(config?: MockConfig): AuthProvider;
|
|
165
|
+
/** API Key provider configuration */
|
|
166
|
+
export interface ApiKeyConfig {
|
|
167
|
+
/** Function to validate API key and return user */
|
|
168
|
+
validate: (apiKey: string) => Promise<User | null>;
|
|
169
|
+
/** Header name to check (default: 'x-api-key') */
|
|
170
|
+
header?: string;
|
|
171
|
+
/** Query parameter name to check */
|
|
172
|
+
queryParam?: string;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* API Key authentication provider.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* apiKey({
|
|
180
|
+
* validate: async (key) => {
|
|
181
|
+
* const apiKey = await db.apiKeys.findByKey(key);
|
|
182
|
+
* if (apiKey && !apiKey.expired) {
|
|
183
|
+
* return { id: apiKey.userId, roles: apiKey.scopes };
|
|
184
|
+
* }
|
|
185
|
+
* return null;
|
|
186
|
+
* }
|
|
187
|
+
* })
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
export declare function apiKey(config: ApiKeyConfig): AuthProvider;
|
|
191
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/providers/index.ts
|
|
3
|
+
function credentials(config) {
|
|
4
|
+
return {
|
|
5
|
+
id: config.id || "credentials",
|
|
6
|
+
name: config.name || "Credentials",
|
|
7
|
+
type: "credentials",
|
|
8
|
+
authorize: config.authorize
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function github(config) {
|
|
12
|
+
const baseUrl = config.enterprise?.baseUrl || "https://github.com";
|
|
13
|
+
const apiBaseUrl = config.enterprise?.baseUrl ? `${config.enterprise.baseUrl}/api/v3` : "https://api.github.com";
|
|
14
|
+
const authorizationUrl = config.authorizationUrl || `${baseUrl}/login/oauth/authorize`;
|
|
15
|
+
const tokenUrl = config.tokenUrl || `${baseUrl}/login/oauth/access_token`;
|
|
16
|
+
const userInfoUrl = config.userInfoUrl || `${apiBaseUrl}/user`;
|
|
17
|
+
const scope = config.scope || ["read:user", "user:email"];
|
|
18
|
+
return {
|
|
19
|
+
id: "github",
|
|
20
|
+
name: "GitHub",
|
|
21
|
+
type: "oauth",
|
|
22
|
+
async authorize(credentials2) {
|
|
23
|
+
if (credentials2.user) {
|
|
24
|
+
return credentials2.user;
|
|
25
|
+
}
|
|
26
|
+
const code = credentials2.code;
|
|
27
|
+
if (!code)
|
|
28
|
+
return null;
|
|
29
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
Accept: "application/json",
|
|
33
|
+
"Content-Type": "application/json"
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
client_id: config.clientId,
|
|
37
|
+
client_secret: config.clientSecret,
|
|
38
|
+
code,
|
|
39
|
+
redirect_uri: config.redirectUri
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
const tokenData = await tokenResponse.json();
|
|
43
|
+
if (!tokenData.access_token)
|
|
44
|
+
return null;
|
|
45
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
46
|
+
headers: {
|
|
47
|
+
Authorization: `token ${tokenData.access_token}`,
|
|
48
|
+
"User-Agent": "EreoJS-Auth",
|
|
49
|
+
Accept: "application/json"
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const user = await userResponse.json();
|
|
53
|
+
let email = user.email;
|
|
54
|
+
if (!email) {
|
|
55
|
+
const emailsResponse = await fetch(`${apiBaseUrl}/user/emails`, {
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `token ${tokenData.access_token}`,
|
|
58
|
+
"User-Agent": "EreoJS-Auth",
|
|
59
|
+
Accept: "application/json"
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const emails = await emailsResponse.json();
|
|
63
|
+
const primaryEmail = emails.find((e) => e.primary && e.verified);
|
|
64
|
+
email = primaryEmail?.email || emails[0]?.email || null;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
id: String(user.id),
|
|
68
|
+
email: email || undefined,
|
|
69
|
+
name: user.name || user.login,
|
|
70
|
+
avatar: user.avatar_url,
|
|
71
|
+
username: user.login
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
getAuthorizationUrl(state, redirectUri) {
|
|
75
|
+
const params = new URLSearchParams({
|
|
76
|
+
client_id: config.clientId,
|
|
77
|
+
redirect_uri: redirectUri,
|
|
78
|
+
scope: scope.join(" "),
|
|
79
|
+
state
|
|
80
|
+
});
|
|
81
|
+
return `${authorizationUrl}?${params.toString()}`;
|
|
82
|
+
},
|
|
83
|
+
async handleCallback(params) {
|
|
84
|
+
const { code, redirectUri } = params;
|
|
85
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: {
|
|
88
|
+
Accept: "application/json",
|
|
89
|
+
"Content-Type": "application/json"
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
client_id: config.clientId,
|
|
93
|
+
client_secret: config.clientSecret,
|
|
94
|
+
code,
|
|
95
|
+
redirect_uri: redirectUri
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
const tokenData = await tokenResponse.json();
|
|
99
|
+
if (!tokenData.access_token)
|
|
100
|
+
return null;
|
|
101
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: `token ${tokenData.access_token}`,
|
|
104
|
+
"User-Agent": "EreoJS-Auth",
|
|
105
|
+
Accept: "application/json"
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const user = await userResponse.json();
|
|
109
|
+
let email = user.email;
|
|
110
|
+
if (!email) {
|
|
111
|
+
const emailsResponse = await fetch(`${apiBaseUrl}/user/emails`, {
|
|
112
|
+
headers: {
|
|
113
|
+
Authorization: `token ${tokenData.access_token}`,
|
|
114
|
+
"User-Agent": "EreoJS-Auth",
|
|
115
|
+
Accept: "application/json"
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const emails = await emailsResponse.json();
|
|
119
|
+
const primaryEmail = emails.find((e) => e.primary && e.verified);
|
|
120
|
+
email = primaryEmail?.email || emails[0]?.email || null;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
id: String(user.id),
|
|
124
|
+
email: email || undefined,
|
|
125
|
+
name: user.name || user.login,
|
|
126
|
+
avatar: user.avatar_url,
|
|
127
|
+
username: user.login
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function google(config) {
|
|
133
|
+
const authorizationUrl = config.authorizationUrl || "https://accounts.google.com/o/oauth2/v2/auth";
|
|
134
|
+
const tokenUrl = config.tokenUrl || "https://oauth2.googleapis.com/token";
|
|
135
|
+
const userInfoUrl = config.userInfoUrl || "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
136
|
+
const scope = config.scope || ["openid", "email", "profile"];
|
|
137
|
+
return {
|
|
138
|
+
id: "google",
|
|
139
|
+
name: "Google",
|
|
140
|
+
type: "oauth",
|
|
141
|
+
async authorize(credentials2) {
|
|
142
|
+
if (credentials2.user) {
|
|
143
|
+
return credentials2.user;
|
|
144
|
+
}
|
|
145
|
+
const code = credentials2.code;
|
|
146
|
+
if (!code)
|
|
147
|
+
return null;
|
|
148
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
152
|
+
},
|
|
153
|
+
body: new URLSearchParams({
|
|
154
|
+
code,
|
|
155
|
+
client_id: config.clientId,
|
|
156
|
+
client_secret: config.clientSecret,
|
|
157
|
+
redirect_uri: config.redirectUri || "postmessage",
|
|
158
|
+
grant_type: "authorization_code"
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
const tokenData = await tokenResponse.json();
|
|
162
|
+
if (!tokenData.access_token)
|
|
163
|
+
return null;
|
|
164
|
+
const userResponse = await fetch(`${userInfoUrl}?access_token=${tokenData.access_token}`);
|
|
165
|
+
const user = await userResponse.json();
|
|
166
|
+
if (config.hostedDomain && user.hd !== config.hostedDomain) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
id: user.id,
|
|
171
|
+
email: user.email,
|
|
172
|
+
name: user.name,
|
|
173
|
+
picture: user.picture,
|
|
174
|
+
emailVerified: user.verified_email
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
getAuthorizationUrl(state, redirectUri) {
|
|
178
|
+
const params = new URLSearchParams({
|
|
179
|
+
client_id: config.clientId,
|
|
180
|
+
redirect_uri: redirectUri,
|
|
181
|
+
response_type: "code",
|
|
182
|
+
scope: scope.join(" "),
|
|
183
|
+
state,
|
|
184
|
+
access_type: "offline",
|
|
185
|
+
prompt: "consent"
|
|
186
|
+
});
|
|
187
|
+
if (config.hostedDomain) {
|
|
188
|
+
params.set("hd", config.hostedDomain);
|
|
189
|
+
}
|
|
190
|
+
return `${authorizationUrl}?${params.toString()}`;
|
|
191
|
+
},
|
|
192
|
+
async handleCallback(params) {
|
|
193
|
+
const { code, redirectUri } = params;
|
|
194
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: {
|
|
197
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
198
|
+
},
|
|
199
|
+
body: new URLSearchParams({
|
|
200
|
+
code,
|
|
201
|
+
client_id: config.clientId,
|
|
202
|
+
client_secret: config.clientSecret,
|
|
203
|
+
redirect_uri: redirectUri,
|
|
204
|
+
grant_type: "authorization_code"
|
|
205
|
+
})
|
|
206
|
+
});
|
|
207
|
+
const tokenData = await tokenResponse.json();
|
|
208
|
+
if (!tokenData.access_token)
|
|
209
|
+
return null;
|
|
210
|
+
const userResponse = await fetch(`${userInfoUrl}?access_token=${tokenData.access_token}`);
|
|
211
|
+
const user = await userResponse.json();
|
|
212
|
+
if (config.hostedDomain && user.hd !== config.hostedDomain) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
id: user.id,
|
|
217
|
+
email: user.email,
|
|
218
|
+
name: user.name,
|
|
219
|
+
picture: user.picture,
|
|
220
|
+
emailVerified: user.verified_email
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function discord(config) {
|
|
226
|
+
const authorizationUrl = config.authorizationUrl || "https://discord.com/api/oauth2/authorize";
|
|
227
|
+
const tokenUrl = config.tokenUrl || "https://discord.com/api/oauth2/token";
|
|
228
|
+
const userInfoUrl = config.userInfoUrl || "https://discord.com/api/users/@me";
|
|
229
|
+
const scope = config.scope || ["identify", "email"];
|
|
230
|
+
return {
|
|
231
|
+
id: "discord",
|
|
232
|
+
name: "Discord",
|
|
233
|
+
type: "oauth",
|
|
234
|
+
async authorize(credentials2) {
|
|
235
|
+
if (credentials2.user) {
|
|
236
|
+
return credentials2.user;
|
|
237
|
+
}
|
|
238
|
+
const code = credentials2.code;
|
|
239
|
+
if (!code)
|
|
240
|
+
return null;
|
|
241
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: {
|
|
244
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
245
|
+
},
|
|
246
|
+
body: new URLSearchParams({
|
|
247
|
+
client_id: config.clientId,
|
|
248
|
+
client_secret: config.clientSecret,
|
|
249
|
+
code,
|
|
250
|
+
grant_type: "authorization_code",
|
|
251
|
+
redirect_uri: config.redirectUri || ""
|
|
252
|
+
})
|
|
253
|
+
});
|
|
254
|
+
const tokenData = await tokenResponse.json();
|
|
255
|
+
if (!tokenData.access_token)
|
|
256
|
+
return null;
|
|
257
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
258
|
+
headers: {
|
|
259
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
const user = await userResponse.json();
|
|
263
|
+
if (config.guildId) {
|
|
264
|
+
const guildsResponse = await fetch("https://discord.com/api/users/@me/guilds", {
|
|
265
|
+
headers: {
|
|
266
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
const guilds = await guildsResponse.json();
|
|
270
|
+
const isMember = guilds.some((g) => g.id === config.guildId);
|
|
271
|
+
if (!isMember)
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const avatarUrl = user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png` : null;
|
|
275
|
+
return {
|
|
276
|
+
id: user.id,
|
|
277
|
+
email: user.email,
|
|
278
|
+
name: user.username,
|
|
279
|
+
avatar: avatarUrl || undefined,
|
|
280
|
+
discriminator: user.discriminator
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
getAuthorizationUrl(state, redirectUri) {
|
|
284
|
+
const params = new URLSearchParams({
|
|
285
|
+
client_id: config.clientId,
|
|
286
|
+
redirect_uri: redirectUri,
|
|
287
|
+
response_type: "code",
|
|
288
|
+
scope: scope.join(" "),
|
|
289
|
+
state
|
|
290
|
+
});
|
|
291
|
+
return `${authorizationUrl}?${params.toString()}`;
|
|
292
|
+
},
|
|
293
|
+
async handleCallback(params) {
|
|
294
|
+
const { code, redirectUri } = params;
|
|
295
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: {
|
|
298
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
299
|
+
},
|
|
300
|
+
body: new URLSearchParams({
|
|
301
|
+
client_id: config.clientId,
|
|
302
|
+
client_secret: config.clientSecret,
|
|
303
|
+
code,
|
|
304
|
+
grant_type: "authorization_code",
|
|
305
|
+
redirect_uri: redirectUri
|
|
306
|
+
})
|
|
307
|
+
});
|
|
308
|
+
const tokenData = await tokenResponse.json();
|
|
309
|
+
if (!tokenData.access_token)
|
|
310
|
+
return null;
|
|
311
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
312
|
+
headers: {
|
|
313
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
const user = await userResponse.json();
|
|
317
|
+
if (config.guildId) {
|
|
318
|
+
const guildsResponse = await fetch("https://discord.com/api/users/@me/guilds", {
|
|
319
|
+
headers: {
|
|
320
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
const guilds = await guildsResponse.json();
|
|
324
|
+
const isMember = guilds.some((g) => g.id === config.guildId);
|
|
325
|
+
if (!isMember)
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const avatarUrl = user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png` : null;
|
|
329
|
+
return {
|
|
330
|
+
id: user.id,
|
|
331
|
+
email: user.email,
|
|
332
|
+
name: user.username,
|
|
333
|
+
avatar: avatarUrl || undefined,
|
|
334
|
+
discriminator: user.discriminator
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function oauth(config) {
|
|
340
|
+
const scope = config.scope || [];
|
|
341
|
+
return {
|
|
342
|
+
id: config.id,
|
|
343
|
+
name: config.name,
|
|
344
|
+
type: "oauth",
|
|
345
|
+
async authorize(credentials2) {
|
|
346
|
+
if (credentials2.user) {
|
|
347
|
+
return credentials2.user;
|
|
348
|
+
}
|
|
349
|
+
const code = credentials2.code;
|
|
350
|
+
if (!code || !config.tokenUrl)
|
|
351
|
+
return null;
|
|
352
|
+
const tokenResponse = await fetch(config.tokenUrl, {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers: {
|
|
355
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
356
|
+
},
|
|
357
|
+
body: new URLSearchParams({
|
|
358
|
+
client_id: config.clientId,
|
|
359
|
+
client_secret: config.clientSecret,
|
|
360
|
+
code,
|
|
361
|
+
grant_type: "authorization_code",
|
|
362
|
+
redirect_uri: config.redirectUri || ""
|
|
363
|
+
})
|
|
364
|
+
});
|
|
365
|
+
const tokenData = await tokenResponse.json();
|
|
366
|
+
if (!tokenData.access_token)
|
|
367
|
+
return null;
|
|
368
|
+
const userInfoUrl = config.userInfoUrl || config.profileUrl;
|
|
369
|
+
if (!userInfoUrl) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
373
|
+
headers: {
|
|
374
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
const profile = await userResponse.json();
|
|
378
|
+
if (config.profile) {
|
|
379
|
+
return config.profile(profile, tokenData);
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
id: profile.id || profile.sub || profile.user_id,
|
|
383
|
+
email: profile.email,
|
|
384
|
+
name: profile.name || profile.username
|
|
385
|
+
};
|
|
386
|
+
},
|
|
387
|
+
getAuthorizationUrl(state, redirectUri) {
|
|
388
|
+
if (!config.authorizationUrl) {
|
|
389
|
+
throw new Error(`Authorization URL not configured for provider: ${config.id}`);
|
|
390
|
+
}
|
|
391
|
+
const params = new URLSearchParams({
|
|
392
|
+
client_id: config.clientId,
|
|
393
|
+
redirect_uri: redirectUri,
|
|
394
|
+
response_type: "code",
|
|
395
|
+
state
|
|
396
|
+
});
|
|
397
|
+
if (scope.length > 0) {
|
|
398
|
+
params.set("scope", scope.join(" "));
|
|
399
|
+
}
|
|
400
|
+
return `${config.authorizationUrl}?${params.toString()}`;
|
|
401
|
+
},
|
|
402
|
+
async handleCallback(params) {
|
|
403
|
+
const { code, redirectUri } = params;
|
|
404
|
+
if (!config.tokenUrl) {
|
|
405
|
+
throw new Error(`Token URL not configured for provider: ${config.id}`);
|
|
406
|
+
}
|
|
407
|
+
const tokenResponse = await fetch(config.tokenUrl, {
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers: {
|
|
410
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
411
|
+
},
|
|
412
|
+
body: new URLSearchParams({
|
|
413
|
+
client_id: config.clientId,
|
|
414
|
+
client_secret: config.clientSecret,
|
|
415
|
+
code,
|
|
416
|
+
grant_type: "authorization_code",
|
|
417
|
+
redirect_uri: redirectUri
|
|
418
|
+
})
|
|
419
|
+
});
|
|
420
|
+
const tokenData = await tokenResponse.json();
|
|
421
|
+
if (!tokenData.access_token)
|
|
422
|
+
return null;
|
|
423
|
+
const userInfoUrl = config.userInfoUrl || config.profileUrl;
|
|
424
|
+
if (!userInfoUrl) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
428
|
+
headers: {
|
|
429
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
const profile = await userResponse.json();
|
|
433
|
+
if (config.profile) {
|
|
434
|
+
return config.profile(profile, tokenData);
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
id: profile.id || profile.sub || profile.user_id,
|
|
438
|
+
email: profile.email,
|
|
439
|
+
name: profile.name || profile.username
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function mock(config) {
|
|
445
|
+
return {
|
|
446
|
+
id: "mock",
|
|
447
|
+
name: "Mock",
|
|
448
|
+
type: "credentials",
|
|
449
|
+
async authorize() {
|
|
450
|
+
if (config?.delay) {
|
|
451
|
+
await new Promise((resolve) => setTimeout(resolve, config.delay));
|
|
452
|
+
}
|
|
453
|
+
if (config?.user) {
|
|
454
|
+
return config.user;
|
|
455
|
+
}
|
|
456
|
+
if (config?.session) {
|
|
457
|
+
return {
|
|
458
|
+
id: config.session.userId,
|
|
459
|
+
email: config.session.email,
|
|
460
|
+
name: config.session.name,
|
|
461
|
+
roles: config.session.roles,
|
|
462
|
+
...config.session.claims
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
id: "mock-user-123",
|
|
467
|
+
email: "mock@example.com",
|
|
468
|
+
name: "Mock User",
|
|
469
|
+
roles: ["user"]
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function apiKey(config) {
|
|
475
|
+
return {
|
|
476
|
+
id: "api-key",
|
|
477
|
+
name: "API Key",
|
|
478
|
+
type: "credentials",
|
|
479
|
+
async authorize(credentials2) {
|
|
480
|
+
const key = credentials2.apiKey;
|
|
481
|
+
if (!key)
|
|
482
|
+
return null;
|
|
483
|
+
return config.validate(key);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
export {
|
|
488
|
+
oauth,
|
|
489
|
+
mock,
|
|
490
|
+
google,
|
|
491
|
+
github,
|
|
492
|
+
discord,
|
|
493
|
+
credentials,
|
|
494
|
+
apiKey
|
|
495
|
+
};
|