@draftlab/auth 0.5.0 → 0.6.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/client.d.mts +45 -2
- package/dist/client.mjs +26 -0
- package/dist/core.mjs +32 -1
- package/dist/provider/apple.d.mts +105 -0
- package/dist/provider/apple.mjs +151 -0
- package/dist/provider/gitlab.d.mts +100 -0
- package/dist/provider/gitlab.mjs +128 -0
- package/dist/provider/reddit.d.mts +101 -0
- package/dist/provider/reddit.mjs +114 -0
- package/dist/provider/slack.d.mts +108 -0
- package/dist/provider/slack.mjs +125 -0
- package/dist/provider/spotify.d.mts +107 -0
- package/dist/provider/spotify.mjs +122 -0
- package/dist/provider/twitch.d.mts +102 -0
- package/dist/provider/twitch.mjs +118 -0
- package/dist/revocation.d.mts +55 -0
- package/dist/revocation.mjs +63 -0
- package/package.json +1 -1
package/dist/client.d.mts
CHANGED
|
@@ -252,8 +252,24 @@ interface VerifyResult<T extends SubjectSchema> {
|
|
|
252
252
|
} }[keyof T];
|
|
253
253
|
}
|
|
254
254
|
/**
|
|
255
|
-
* Options for
|
|
255
|
+
* Options for token revocation.
|
|
256
256
|
*/
|
|
257
|
+
interface RevokeOptions {
|
|
258
|
+
/**
|
|
259
|
+
* Optional hint about the token type.
|
|
260
|
+
* Can be "access_token" or "refresh_token".
|
|
261
|
+
*
|
|
262
|
+
* Helps the server optimize token lookup.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* {
|
|
267
|
+
* tokenTypeHint: "refresh_token"
|
|
268
|
+
* }
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
tokenTypeHint?: "access_token" | "refresh_token";
|
|
272
|
+
}
|
|
257
273
|
/**
|
|
258
274
|
* Draft Auth client with OAuth 2.0 operations.
|
|
259
275
|
*/
|
|
@@ -392,6 +408,33 @@ interface Client {
|
|
|
392
408
|
* ```
|
|
393
409
|
*/
|
|
394
410
|
verify<T extends SubjectSchema>(subjects: T, token: string, options?: VerifyOptions): Promise<Result<VerifyResult<T>, InvalidRefreshTokenError | InvalidAccessTokenError | InvalidSubjectError>>;
|
|
411
|
+
/**
|
|
412
|
+
* Revoke a token (access or refresh token).
|
|
413
|
+
*
|
|
414
|
+
* Once revoked, the token cannot be used to access resources or refresh.
|
|
415
|
+
* Useful for implementing logout functionality.
|
|
416
|
+
*
|
|
417
|
+
* @param token - The token to revoke
|
|
418
|
+
* @param opts - Additional revocation options
|
|
419
|
+
* @returns Empty result on success
|
|
420
|
+
*
|
|
421
|
+
* @example Logout with refresh token revocation
|
|
422
|
+
* ```ts
|
|
423
|
+
* const result = await client.revoke(refreshToken, {
|
|
424
|
+
* tokenTypeHint: "refresh_token"
|
|
425
|
+
* })
|
|
426
|
+
*
|
|
427
|
+
* if (result.success) {
|
|
428
|
+
* // Token revoked successfully, user is logged out
|
|
429
|
+
* clearStoredTokens()
|
|
430
|
+
* redirectToHome()
|
|
431
|
+
* } else {
|
|
432
|
+
* // Revocation failed, but still clear tokens on client
|
|
433
|
+
* clearStoredTokens()
|
|
434
|
+
* }
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
revoke(token: string, opts?: RevokeOptions): Promise<Result<void>>;
|
|
395
438
|
}
|
|
396
439
|
/**
|
|
397
440
|
* Create a Draft Auth client.
|
|
@@ -409,4 +452,4 @@ interface Client {
|
|
|
409
452
|
*/
|
|
410
453
|
declare const createClient: (input: ClientInput) => Client;
|
|
411
454
|
//#endregion
|
|
412
|
-
export { AuthorizeOptions, AuthorizeResult, Challenge, Client, ClientInput, RefreshOptions, Result, Tokens, VerifyOptions, VerifyResult, WellKnown, createClient };
|
|
455
|
+
export { AuthorizeOptions, AuthorizeResult, Challenge, Client, ClientInput, RefreshOptions, Result, RevokeOptions, Tokens, VerifyOptions, VerifyResult, WellKnown, createClient };
|
package/dist/client.mjs
CHANGED
|
@@ -245,6 +245,32 @@ const createClient = (input) => {
|
|
|
245
245
|
error: new InvalidAccessTokenError()
|
|
246
246
|
};
|
|
247
247
|
}
|
|
248
|
+
},
|
|
249
|
+
async revoke(token, opts) {
|
|
250
|
+
try {
|
|
251
|
+
const wk = await getIssuer();
|
|
252
|
+
const body = new URLSearchParams({
|
|
253
|
+
token,
|
|
254
|
+
...opts?.tokenTypeHint ? { token_type_hint: opts.tokenTypeHint } : {}
|
|
255
|
+
});
|
|
256
|
+
if ((await f(wk.token_endpoint.replace("/token", "/revoke"), {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
259
|
+
body: body.toString()
|
|
260
|
+
})).ok) return {
|
|
261
|
+
success: true,
|
|
262
|
+
data: void 0
|
|
263
|
+
};
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
error: /* @__PURE__ */ new Error("Failed to revoke token")
|
|
267
|
+
};
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return {
|
|
270
|
+
success: false,
|
|
271
|
+
error
|
|
272
|
+
};
|
|
273
|
+
}
|
|
248
274
|
}
|
|
249
275
|
};
|
|
250
276
|
return client;
|
package/dist/core.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { generateSecureToken } from "./random.mjs";
|
|
|
6
6
|
import { Storage } from "./storage/storage.mjs";
|
|
7
7
|
import { encryptionKeys, signingKeys } from "./keys.mjs";
|
|
8
8
|
import { PluginManager } from "./plugin/manager.mjs";
|
|
9
|
+
import { Revocation } from "./revocation.mjs";
|
|
9
10
|
import { setTheme } from "./themes/theme.mjs";
|
|
10
11
|
import { Select } from "./ui/select.mjs";
|
|
11
12
|
import { CompactEncrypt, SignJWT, compactDecrypt } from "jose";
|
|
@@ -384,7 +385,12 @@ const issuer = (input) => {
|
|
|
384
385
|
const error$1 = new OauthError("invalid_request", "Missing refresh_token");
|
|
385
386
|
return c.json(error$1.toJSON(), { status: 400 });
|
|
386
387
|
}
|
|
387
|
-
const
|
|
388
|
+
const refreshTokenStr = refreshToken.toString();
|
|
389
|
+
if (await Revocation.isRevoked(storage, refreshTokenStr)) {
|
|
390
|
+
const error$1 = new OauthError("invalid_grant", "Refresh token has been revoked");
|
|
391
|
+
return c.json(error$1.toJSON(), { status: 400 });
|
|
392
|
+
}
|
|
393
|
+
const splits = refreshTokenStr.split(":");
|
|
388
394
|
const token = splits.pop();
|
|
389
395
|
if (!token) throw new Error("Invalid refresh token format");
|
|
390
396
|
const subject = splits.join(":");
|
|
@@ -458,6 +464,31 @@ const issuer = (input) => {
|
|
|
458
464
|
}, { status: 400 });
|
|
459
465
|
}
|
|
460
466
|
});
|
|
467
|
+
app.post("/revoke", {
|
|
468
|
+
middleware: [cors({
|
|
469
|
+
origin: "*",
|
|
470
|
+
allowHeaders: ["Content-Type"],
|
|
471
|
+
allowMethods: ["POST"],
|
|
472
|
+
credentials: false
|
|
473
|
+
})],
|
|
474
|
+
handler: async (c) => {
|
|
475
|
+
const token = (await c.formData()).get("token")?.toString();
|
|
476
|
+
if (!token) {
|
|
477
|
+
const error$1 = new OauthError("invalid_request", "Missing token parameter");
|
|
478
|
+
return c.json(error$1.toJSON(), { status: 400 });
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const expiresAt = Date.now() + ttlRefresh * 1e3;
|
|
482
|
+
await Revocation.revoke(storage, token, expiresAt);
|
|
483
|
+
return c.json({});
|
|
484
|
+
} catch (_err) {
|
|
485
|
+
return c.json({
|
|
486
|
+
error: "server_error",
|
|
487
|
+
error_description: "Token revocation failed"
|
|
488
|
+
}, { status: 500 });
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
});
|
|
461
492
|
app.get("/authorize", async (c) => {
|
|
462
493
|
const provider = c.query("provider");
|
|
463
494
|
const response_type = c.query("response_type");
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Provider } from "./provider.mjs";
|
|
2
|
+
import { Oauth2UserData, Oauth2WrappedConfig } from "./oauth2.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/provider/apple.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration options for Apple OAuth 2.0 provider.
|
|
8
|
+
* Extends the base OAuth 2.0 configuration with Apple-specific documentation.
|
|
9
|
+
*/
|
|
10
|
+
interface AppleConfig extends Oauth2WrappedConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Apple Service ID (app identifier for your Sign in with Apple implementation).
|
|
13
|
+
* Get this from your Apple Developer account when creating a Service ID.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* {
|
|
18
|
+
* clientID: "com.example.app.signin"
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
readonly clientID: string;
|
|
23
|
+
/**
|
|
24
|
+
* Apple client secret (JWT token signed with your private key).
|
|
25
|
+
* This is different from other providers - Apple requires a JWT token
|
|
26
|
+
* generated from your private key.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* {
|
|
31
|
+
* clientSecret: process.env.APPLE_CLIENT_SECRET
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
readonly clientSecret: string;
|
|
36
|
+
/**
|
|
37
|
+
* Apple OAuth scopes to request access for.
|
|
38
|
+
* Apple only supports "name" and "email" scopes.
|
|
39
|
+
*
|
|
40
|
+
* Important: Apple only provides user data (name, email) on the FIRST authorization.
|
|
41
|
+
* Subsequent authorizations won't include this data.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* {
|
|
46
|
+
* scopes: ["name", "email"]
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
readonly scopes: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates an Apple OAuth 2.0 authentication provider.
|
|
54
|
+
* Allows users to authenticate using their Apple accounts.
|
|
55
|
+
*
|
|
56
|
+
* @param config - Apple OAuth 2.0 configuration
|
|
57
|
+
* @returns OAuth 2.0 provider configured for Apple
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* // Basic Apple authentication
|
|
62
|
+
* const basicApple = AppleProvider({
|
|
63
|
+
* clientID: process.env.APPLE_CLIENT_ID,
|
|
64
|
+
* clientSecret: process.env.APPLE_CLIENT_SECRET
|
|
65
|
+
* })
|
|
66
|
+
*
|
|
67
|
+
* // Apple with name and email scopes
|
|
68
|
+
* const appleWithScopes = AppleProvider({
|
|
69
|
+
* clientID: process.env.APPLE_CLIENT_ID,
|
|
70
|
+
* clientSecret: process.env.APPLE_CLIENT_SECRET,
|
|
71
|
+
* scopes: ["name", "email"]
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* // Using the tokens and id_token
|
|
75
|
+
* export default issuer({
|
|
76
|
+
* providers: { apple: appleWithScopes },
|
|
77
|
+
* success: async (ctx, value) => {
|
|
78
|
+
* if (value.provider === "apple") {
|
|
79
|
+
* // Apple returns user data in the initial authorization response
|
|
80
|
+
* // You need to decode the id_token to extract user information
|
|
81
|
+
*
|
|
82
|
+
* // The id_token contains:
|
|
83
|
+
* // - sub: unique Apple user identifier
|
|
84
|
+
* // - email: user email (only on first authorization)
|
|
85
|
+
* // - email_verified: whether email is verified
|
|
86
|
+
* // - is_private_email: whether user used private relay
|
|
87
|
+
*
|
|
88
|
+
* // Decode and verify the id_token using jose:
|
|
89
|
+
* // const verified = await jwtVerify(value.tokenset.id, jwks)
|
|
90
|
+
* // const user = verified.payload
|
|
91
|
+
*
|
|
92
|
+
* return ctx.subject("user", {
|
|
93
|
+
* appleId: user.sub,
|
|
94
|
+
* email: user.email,
|
|
95
|
+
* emailVerified: user.email_verified,
|
|
96
|
+
* isPrivateEmail: user.is_private_email
|
|
97
|
+
* })
|
|
98
|
+
* }
|
|
99
|
+
* }
|
|
100
|
+
* })
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare const AppleProvider: (config: AppleConfig) => Provider<Oauth2UserData>;
|
|
104
|
+
//#endregion
|
|
105
|
+
export { AppleConfig, AppleProvider };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Oauth2Provider } from "./oauth2.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/provider/apple.ts
|
|
4
|
+
/**
|
|
5
|
+
* Apple authentication provider for Draft Auth.
|
|
6
|
+
* Implements OAuth 2.0 flow for authenticating users with their Apple accounts.
|
|
7
|
+
*
|
|
8
|
+
* ## Quick Setup
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { AppleProvider } from "@draftlab/auth/provider/apple"
|
|
12
|
+
*
|
|
13
|
+
* export default issuer({
|
|
14
|
+
* providers: {
|
|
15
|
+
* apple: AppleProvider({
|
|
16
|
+
* clientID: process.env.APPLE_CLIENT_ID,
|
|
17
|
+
* clientSecret: process.env.APPLE_CLIENT_SECRET,
|
|
18
|
+
* scopes: ["name", "email"]
|
|
19
|
+
* })
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Setup Instructions
|
|
25
|
+
*
|
|
26
|
+
* ### 1. Create App ID
|
|
27
|
+
* - Go to [Apple Developer](https://developer.apple.com)
|
|
28
|
+
* - Create a new App ID with "Sign in with Apple" capability
|
|
29
|
+
*
|
|
30
|
+
* ### 2. Create Service ID
|
|
31
|
+
* - Create a new Service ID (this is your clientID)
|
|
32
|
+
* - Configure "Sign in with Apple"
|
|
33
|
+
* - Add your redirect URI
|
|
34
|
+
*
|
|
35
|
+
* ### 3. Create Private Key
|
|
36
|
+
* - Create a private key for "Sign in with Apple"
|
|
37
|
+
* - Download the .p8 file (this is used to create your clientSecret)
|
|
38
|
+
*
|
|
39
|
+
* ## Client Secret Generation
|
|
40
|
+
*
|
|
41
|
+
* Apple requires a JWT token as the client secret. You'll need:
|
|
42
|
+
* - Key ID from the private key
|
|
43
|
+
* - Team ID from your Apple Developer account
|
|
44
|
+
* - Private key (.p8 file)
|
|
45
|
+
*
|
|
46
|
+
* Use a library to generate the JWT (valid for ~15 minutes):
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { SignJWT } from "jose"
|
|
50
|
+
*
|
|
51
|
+
* const secret = await new SignJWT({
|
|
52
|
+
* iss: "YOUR_TEAM_ID",
|
|
53
|
+
* aud: "https://appleid.apple.com",
|
|
54
|
+
* sub: process.env.APPLE_CLIENT_ID,
|
|
55
|
+
* iat: Math.floor(Date.now() / 1000),
|
|
56
|
+
* exp: Math.floor(Date.now() / 1000) + 15 * 60
|
|
57
|
+
* })
|
|
58
|
+
* .setProtectedHeader({ alg: "ES256", kid: "YOUR_KEY_ID" })
|
|
59
|
+
* .sign(privateKey)
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* ## Common Scopes
|
|
63
|
+
*
|
|
64
|
+
* - `name` - Access user's name (first and last name)
|
|
65
|
+
* - `email` - Access user's email address
|
|
66
|
+
*
|
|
67
|
+
* Note: Apple only returns user data on the first authorization. Subsequent authorizations won't include name/email.
|
|
68
|
+
*
|
|
69
|
+
* ## User Data Access
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* success: async (ctx, value) => {
|
|
73
|
+
* if (value.provider === "apple") {
|
|
74
|
+
* const accessToken = value.tokenset.access
|
|
75
|
+
*
|
|
76
|
+
* // Apple doesn't provide a userinfo endpoint
|
|
77
|
+
* // User data is returned in the authorization response
|
|
78
|
+
* // You need to parse the id_token JWT to get user info
|
|
79
|
+
*
|
|
80
|
+
* // For subsequent logins without name/email, use the subject (user_id)
|
|
81
|
+
* // from the ID token to identify the user
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @packageDocumentation
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* Creates an Apple OAuth 2.0 authentication provider.
|
|
90
|
+
* Allows users to authenticate using their Apple accounts.
|
|
91
|
+
*
|
|
92
|
+
* @param config - Apple OAuth 2.0 configuration
|
|
93
|
+
* @returns OAuth 2.0 provider configured for Apple
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* // Basic Apple authentication
|
|
98
|
+
* const basicApple = AppleProvider({
|
|
99
|
+
* clientID: process.env.APPLE_CLIENT_ID,
|
|
100
|
+
* clientSecret: process.env.APPLE_CLIENT_SECRET
|
|
101
|
+
* })
|
|
102
|
+
*
|
|
103
|
+
* // Apple with name and email scopes
|
|
104
|
+
* const appleWithScopes = AppleProvider({
|
|
105
|
+
* clientID: process.env.APPLE_CLIENT_ID,
|
|
106
|
+
* clientSecret: process.env.APPLE_CLIENT_SECRET,
|
|
107
|
+
* scopes: ["name", "email"]
|
|
108
|
+
* })
|
|
109
|
+
*
|
|
110
|
+
* // Using the tokens and id_token
|
|
111
|
+
* export default issuer({
|
|
112
|
+
* providers: { apple: appleWithScopes },
|
|
113
|
+
* success: async (ctx, value) => {
|
|
114
|
+
* if (value.provider === "apple") {
|
|
115
|
+
* // Apple returns user data in the initial authorization response
|
|
116
|
+
* // You need to decode the id_token to extract user information
|
|
117
|
+
*
|
|
118
|
+
* // The id_token contains:
|
|
119
|
+
* // - sub: unique Apple user identifier
|
|
120
|
+
* // - email: user email (only on first authorization)
|
|
121
|
+
* // - email_verified: whether email is verified
|
|
122
|
+
* // - is_private_email: whether user used private relay
|
|
123
|
+
*
|
|
124
|
+
* // Decode and verify the id_token using jose:
|
|
125
|
+
* // const verified = await jwtVerify(value.tokenset.id, jwks)
|
|
126
|
+
* // const user = verified.payload
|
|
127
|
+
*
|
|
128
|
+
* return ctx.subject("user", {
|
|
129
|
+
* appleId: user.sub,
|
|
130
|
+
* email: user.email,
|
|
131
|
+
* emailVerified: user.email_verified,
|
|
132
|
+
* isPrivateEmail: user.is_private_email
|
|
133
|
+
* })
|
|
134
|
+
* }
|
|
135
|
+
* }
|
|
136
|
+
* })
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
const AppleProvider = (config) => {
|
|
140
|
+
return Oauth2Provider({
|
|
141
|
+
...config,
|
|
142
|
+
type: "apple",
|
|
143
|
+
endpoint: {
|
|
144
|
+
authorization: "https://appleid.apple.com/auth/authorize",
|
|
145
|
+
token: "https://appleid.apple.com/auth/token"
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
//#endregion
|
|
151
|
+
export { AppleProvider };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Provider } from "./provider.mjs";
|
|
2
|
+
import { Oauth2UserData, Oauth2WrappedConfig } from "./oauth2.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/provider/gitlab.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration options for GitLab OAuth 2.0 provider.
|
|
8
|
+
* Extends the base OAuth 2.0 configuration with GitLab-specific documentation.
|
|
9
|
+
*/
|
|
10
|
+
interface GitlabConfig extends Oauth2WrappedConfig {
|
|
11
|
+
/**
|
|
12
|
+
* GitLab application client ID.
|
|
13
|
+
* Get this from your GitLab application settings.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* {
|
|
18
|
+
* clientID: "abcdef123456"
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
readonly clientID: string;
|
|
23
|
+
/**
|
|
24
|
+
* GitLab application client secret.
|
|
25
|
+
* Keep this secure and never expose it to client-side code.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* {
|
|
30
|
+
* clientSecret: process.env.GITLAB_CLIENT_SECRET
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
readonly clientSecret: string;
|
|
35
|
+
/**
|
|
36
|
+
* GitLab 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
|
+
* "read_user", // Access user profile
|
|
44
|
+
* "read_api", // Read-access to API
|
|
45
|
+
* "read_repository" // Access repositories
|
|
46
|
+
* ]
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
readonly scopes: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates a GitLab OAuth 2.0 authentication provider.
|
|
54
|
+
* Allows users to authenticate using their GitLab accounts (gitlab.com or self-hosted).
|
|
55
|
+
*
|
|
56
|
+
* @param config - GitLab OAuth 2.0 configuration
|
|
57
|
+
* @returns OAuth 2.0 provider configured for GitLab
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* // Basic GitLab.com authentication
|
|
62
|
+
* const basicGitlab = GitlabProvider({
|
|
63
|
+
* clientID: process.env.GITLAB_CLIENT_ID,
|
|
64
|
+
* clientSecret: process.env.GITLAB_CLIENT_SECRET
|
|
65
|
+
* })
|
|
66
|
+
*
|
|
67
|
+
* // GitLab with read access
|
|
68
|
+
* const gitlabWithRead = GitlabProvider({
|
|
69
|
+
* clientID: process.env.GITLAB_CLIENT_ID,
|
|
70
|
+
* clientSecret: process.env.GITLAB_CLIENT_SECRET,
|
|
71
|
+
* scopes: ["read_user", "read_api"]
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* // Using the access token to fetch user data
|
|
75
|
+
* export default issuer({
|
|
76
|
+
* providers: { gitlab: gitlabWithRead },
|
|
77
|
+
* success: async (ctx, value) => {
|
|
78
|
+
* if (value.provider === "gitlab") {
|
|
79
|
+
* const token = value.tokenset.access
|
|
80
|
+
*
|
|
81
|
+
* const userRes = await fetch('https://gitlab.com/api/v4/user', {
|
|
82
|
+
* headers: { Authorization: `Bearer ${token}` }
|
|
83
|
+
* })
|
|
84
|
+
* const user = await userRes.json()
|
|
85
|
+
*
|
|
86
|
+
* return ctx.subject("user", {
|
|
87
|
+
* gitlabId: user.id,
|
|
88
|
+
* username: user.username,
|
|
89
|
+
* email: user.email,
|
|
90
|
+
* name: user.name,
|
|
91
|
+
* avatar: user.avatar_url
|
|
92
|
+
* })
|
|
93
|
+
* }
|
|
94
|
+
* }
|
|
95
|
+
* })
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
declare const GitlabProvider: (config: GitlabConfig) => Provider<Oauth2UserData>;
|
|
99
|
+
//#endregion
|
|
100
|
+
export { GitlabConfig, GitlabProvider };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Oauth2Provider } from "./oauth2.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/provider/gitlab.ts
|
|
4
|
+
/**
|
|
5
|
+
* GitLab authentication provider for Draft Auth.
|
|
6
|
+
* Implements OAuth 2.0 flow for authenticating users with their GitLab accounts.
|
|
7
|
+
*
|
|
8
|
+
* ## Quick Setup
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { GitlabProvider } from "@draftlab/auth/provider/gitlab"
|
|
12
|
+
*
|
|
13
|
+
* export default issuer({
|
|
14
|
+
* providers: {
|
|
15
|
+
* gitlab: GitlabProvider({
|
|
16
|
+
* clientID: process.env.GITLAB_CLIENT_ID,
|
|
17
|
+
* clientSecret: process.env.GITLAB_CLIENT_SECRET,
|
|
18
|
+
* scopes: ["read_user", "read_api"]
|
|
19
|
+
* })
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Common Scopes
|
|
25
|
+
*
|
|
26
|
+
* - `read_user` - Access user profile
|
|
27
|
+
* - `read_api` - Read-access to the API
|
|
28
|
+
* - `read_repository` - Access to project repositories
|
|
29
|
+
* - `write_repository` - Write access to repositories
|
|
30
|
+
* - `api` - Full API access
|
|
31
|
+
* - `read_user_email` - Access user email
|
|
32
|
+
*
|
|
33
|
+
* ## Self-Hosted GitLab
|
|
34
|
+
*
|
|
35
|
+
* For self-hosted GitLab instances, you can override the endpoint URLs:
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* const selfHostedGitlab = Oauth2Provider({
|
|
39
|
+
* clientID: process.env.GITLAB_CLIENT_ID,
|
|
40
|
+
* clientSecret: process.env.GITLAB_CLIENT_SECRET,
|
|
41
|
+
* scopes: ["read_user"],
|
|
42
|
+
* type: "gitlab",
|
|
43
|
+
* endpoint: {
|
|
44
|
+
* authorization: "https://your-gitlab.com/oauth/authorize",
|
|
45
|
+
* token: "https://your-gitlab.com/oauth/token"
|
|
46
|
+
* }
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* ## User Data Access
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* success: async (ctx, value) => {
|
|
54
|
+
* if (value.provider === "gitlab") {
|
|
55
|
+
* const accessToken = value.tokenset.access
|
|
56
|
+
*
|
|
57
|
+
* // Fetch user information
|
|
58
|
+
* const userResponse = await fetch('https://gitlab.com/api/v4/user', {
|
|
59
|
+
* headers: { Authorization: `Bearer ${accessToken}` }
|
|
60
|
+
* })
|
|
61
|
+
* const user = await userResponse.json()
|
|
62
|
+
*
|
|
63
|
+
* // User info: id, username, email, name, avatar_url
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @packageDocumentation
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* Creates a GitLab OAuth 2.0 authentication provider.
|
|
72
|
+
* Allows users to authenticate using their GitLab accounts (gitlab.com or self-hosted).
|
|
73
|
+
*
|
|
74
|
+
* @param config - GitLab OAuth 2.0 configuration
|
|
75
|
+
* @returns OAuth 2.0 provider configured for GitLab
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* // Basic GitLab.com authentication
|
|
80
|
+
* const basicGitlab = GitlabProvider({
|
|
81
|
+
* clientID: process.env.GITLAB_CLIENT_ID,
|
|
82
|
+
* clientSecret: process.env.GITLAB_CLIENT_SECRET
|
|
83
|
+
* })
|
|
84
|
+
*
|
|
85
|
+
* // GitLab with read access
|
|
86
|
+
* const gitlabWithRead = GitlabProvider({
|
|
87
|
+
* clientID: process.env.GITLAB_CLIENT_ID,
|
|
88
|
+
* clientSecret: process.env.GITLAB_CLIENT_SECRET,
|
|
89
|
+
* scopes: ["read_user", "read_api"]
|
|
90
|
+
* })
|
|
91
|
+
*
|
|
92
|
+
* // Using the access token to fetch user data
|
|
93
|
+
* export default issuer({
|
|
94
|
+
* providers: { gitlab: gitlabWithRead },
|
|
95
|
+
* success: async (ctx, value) => {
|
|
96
|
+
* if (value.provider === "gitlab") {
|
|
97
|
+
* const token = value.tokenset.access
|
|
98
|
+
*
|
|
99
|
+
* const userRes = await fetch('https://gitlab.com/api/v4/user', {
|
|
100
|
+
* headers: { Authorization: `Bearer ${token}` }
|
|
101
|
+
* })
|
|
102
|
+
* const user = await userRes.json()
|
|
103
|
+
*
|
|
104
|
+
* return ctx.subject("user", {
|
|
105
|
+
* gitlabId: user.id,
|
|
106
|
+
* username: user.username,
|
|
107
|
+
* email: user.email,
|
|
108
|
+
* name: user.name,
|
|
109
|
+
* avatar: user.avatar_url
|
|
110
|
+
* })
|
|
111
|
+
* }
|
|
112
|
+
* }
|
|
113
|
+
* })
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
const GitlabProvider = (config) => {
|
|
117
|
+
return Oauth2Provider({
|
|
118
|
+
...config,
|
|
119
|
+
type: "gitlab",
|
|
120
|
+
endpoint: {
|
|
121
|
+
authorization: "https://gitlab.com/oauth/authorize",
|
|
122
|
+
token: "https://gitlab.com/oauth/token"
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
export { GitlabProvider };
|