@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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Provider } from "./provider.mjs";
|
|
2
|
+
import { Oauth2UserData, Oauth2WrappedConfig } from "./oauth2.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/provider/twitch.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration options for Twitch OAuth 2.0 provider.
|
|
8
|
+
* Extends the base OAuth 2.0 configuration with Twitch-specific documentation.
|
|
9
|
+
*/
|
|
10
|
+
interface TwitchConfig extends Oauth2WrappedConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Twitch application client ID.
|
|
13
|
+
* Get this from your Twitch Console at https://dev.twitch.tv/console
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* {
|
|
18
|
+
* clientID: "abcdef123456"
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
readonly clientID: string;
|
|
23
|
+
/**
|
|
24
|
+
* Twitch 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.TWITCH_CLIENT_SECRET
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
readonly clientSecret: string;
|
|
35
|
+
/**
|
|
36
|
+
* Twitch 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
|
+
* "user:read:email", // Access user email
|
|
44
|
+
* "user:read:subscriptions" // View subscriptions
|
|
45
|
+
* ]
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
readonly scopes: string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates a Twitch OAuth 2.0 authentication provider.
|
|
53
|
+
* Allows users to authenticate using their Twitch accounts.
|
|
54
|
+
*
|
|
55
|
+
* @param config - Twitch OAuth 2.0 configuration
|
|
56
|
+
* @returns OAuth 2.0 provider configured for Twitch
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* // Basic Twitch authentication
|
|
61
|
+
* const basicTwitch = TwitchProvider({
|
|
62
|
+
* clientID: process.env.TWITCH_CLIENT_ID,
|
|
63
|
+
* clientSecret: process.env.TWITCH_CLIENT_SECRET
|
|
64
|
+
* })
|
|
65
|
+
*
|
|
66
|
+
* // Twitch with email scope
|
|
67
|
+
* const twitchWithEmail = TwitchProvider({
|
|
68
|
+
* clientID: process.env.TWITCH_CLIENT_ID,
|
|
69
|
+
* clientSecret: process.env.TWITCH_CLIENT_SECRET,
|
|
70
|
+
* scopes: ["user:read:email"]
|
|
71
|
+
* })
|
|
72
|
+
*
|
|
73
|
+
* // Using the access token to fetch user data
|
|
74
|
+
* export default issuer({
|
|
75
|
+
* providers: { twitch: twitchWithEmail },
|
|
76
|
+
* success: async (ctx, value) => {
|
|
77
|
+
* if (value.provider === "twitch") {
|
|
78
|
+
* const token = value.tokenset.access
|
|
79
|
+
*
|
|
80
|
+
* const userRes = await fetch('https://api.twitch.tv/helix/users', {
|
|
81
|
+
* headers: {
|
|
82
|
+
* 'Authorization': `Bearer ${token}`,
|
|
83
|
+
* 'Client-ID': process.env.TWITCH_CLIENT_ID
|
|
84
|
+
* }
|
|
85
|
+
* })
|
|
86
|
+
* const { data } = await userRes.json()
|
|
87
|
+
* const user = data[0]
|
|
88
|
+
*
|
|
89
|
+
* return ctx.subject("user", {
|
|
90
|
+
* twitchId: user.id,
|
|
91
|
+
* login: user.login,
|
|
92
|
+
* email: user.email,
|
|
93
|
+
* displayName: user.display_name
|
|
94
|
+
* })
|
|
95
|
+
* }
|
|
96
|
+
* }
|
|
97
|
+
* })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
declare const TwitchProvider: (config: TwitchConfig) => Provider<Oauth2UserData>;
|
|
101
|
+
//#endregion
|
|
102
|
+
export { TwitchConfig, TwitchProvider };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Oauth2Provider } from "./oauth2.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/provider/twitch.ts
|
|
4
|
+
/**
|
|
5
|
+
* Twitch authentication provider for Draft Auth.
|
|
6
|
+
* Implements OAuth 2.0 flow for authenticating users with their Twitch accounts.
|
|
7
|
+
*
|
|
8
|
+
* ## Quick Setup
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { TwitchProvider } from "@draftlab/auth/provider/twitch"
|
|
12
|
+
*
|
|
13
|
+
* export default issuer({
|
|
14
|
+
* providers: {
|
|
15
|
+
* twitch: TwitchProvider({
|
|
16
|
+
* clientID: process.env.TWITCH_CLIENT_ID,
|
|
17
|
+
* clientSecret: process.env.TWITCH_CLIENT_SECRET,
|
|
18
|
+
* scopes: ["user:read:email"]
|
|
19
|
+
* })
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Common Scopes
|
|
25
|
+
*
|
|
26
|
+
* - `user:read:email` - Access user's email address
|
|
27
|
+
* - `user:read:subscriptions` - View user subscriptions
|
|
28
|
+
* - `user:read:follows` - View user's follows
|
|
29
|
+
* - `channel:read:subscriptions` - View channel subscribers
|
|
30
|
+
* - `analytics:read:games` - View game analytics
|
|
31
|
+
* - `bits:read` - View bits information
|
|
32
|
+
*
|
|
33
|
+
* ## User Data Access
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* success: async (ctx, value) => {
|
|
37
|
+
* if (value.provider === "twitch") {
|
|
38
|
+
* const accessToken = value.tokenset.access
|
|
39
|
+
*
|
|
40
|
+
* // Fetch user information
|
|
41
|
+
* const userResponse = await fetch('https://api.twitch.tv/helix/users', {
|
|
42
|
+
* headers: {
|
|
43
|
+
* 'Authorization': `Bearer ${accessToken}`,
|
|
44
|
+
* 'Client-ID': process.env.TWITCH_CLIENT_ID
|
|
45
|
+
* }
|
|
46
|
+
* })
|
|
47
|
+
* const { data } = await userResponse.json()
|
|
48
|
+
* const user = data[0]
|
|
49
|
+
*
|
|
50
|
+
* // User info available: id, login, display_name, email, profile_image_url
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @packageDocumentation
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Creates a Twitch OAuth 2.0 authentication provider.
|
|
59
|
+
* Allows users to authenticate using their Twitch accounts.
|
|
60
|
+
*
|
|
61
|
+
* @param config - Twitch OAuth 2.0 configuration
|
|
62
|
+
* @returns OAuth 2.0 provider configured for Twitch
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* // Basic Twitch authentication
|
|
67
|
+
* const basicTwitch = TwitchProvider({
|
|
68
|
+
* clientID: process.env.TWITCH_CLIENT_ID,
|
|
69
|
+
* clientSecret: process.env.TWITCH_CLIENT_SECRET
|
|
70
|
+
* })
|
|
71
|
+
*
|
|
72
|
+
* // Twitch with email scope
|
|
73
|
+
* const twitchWithEmail = TwitchProvider({
|
|
74
|
+
* clientID: process.env.TWITCH_CLIENT_ID,
|
|
75
|
+
* clientSecret: process.env.TWITCH_CLIENT_SECRET,
|
|
76
|
+
* scopes: ["user:read:email"]
|
|
77
|
+
* })
|
|
78
|
+
*
|
|
79
|
+
* // Using the access token to fetch user data
|
|
80
|
+
* export default issuer({
|
|
81
|
+
* providers: { twitch: twitchWithEmail },
|
|
82
|
+
* success: async (ctx, value) => {
|
|
83
|
+
* if (value.provider === "twitch") {
|
|
84
|
+
* const token = value.tokenset.access
|
|
85
|
+
*
|
|
86
|
+
* const userRes = await fetch('https://api.twitch.tv/helix/users', {
|
|
87
|
+
* headers: {
|
|
88
|
+
* 'Authorization': `Bearer ${token}`,
|
|
89
|
+
* 'Client-ID': process.env.TWITCH_CLIENT_ID
|
|
90
|
+
* }
|
|
91
|
+
* })
|
|
92
|
+
* const { data } = await userRes.json()
|
|
93
|
+
* const user = data[0]
|
|
94
|
+
*
|
|
95
|
+
* return ctx.subject("user", {
|
|
96
|
+
* twitchId: user.id,
|
|
97
|
+
* login: user.login,
|
|
98
|
+
* email: user.email,
|
|
99
|
+
* displayName: user.display_name
|
|
100
|
+
* })
|
|
101
|
+
* }
|
|
102
|
+
* }
|
|
103
|
+
* })
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
const TwitchProvider = (config) => {
|
|
107
|
+
return Oauth2Provider({
|
|
108
|
+
...config,
|
|
109
|
+
type: "twitch",
|
|
110
|
+
endpoint: {
|
|
111
|
+
authorization: "https://id.twitch.tv/oauth2/authorize",
|
|
112
|
+
token: "https://id.twitch.tv/oauth2/token"
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
export { TwitchProvider };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StorageAdapter } from "./storage/storage.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/revocation.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Data stored for a revoked token.
|
|
7
|
+
* Tracks when the token was revoked and when it naturally expires.
|
|
8
|
+
*/
|
|
9
|
+
interface RevocationRecord {
|
|
10
|
+
/** Timestamp when the token was revoked (milliseconds) */
|
|
11
|
+
revokedAt: number;
|
|
12
|
+
/** Timestamp when the token naturally expires (milliseconds) */
|
|
13
|
+
expiresAt: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Token revocation manager.
|
|
17
|
+
* Provides methods to revoke tokens and check if a token has been revoked.
|
|
18
|
+
*/
|
|
19
|
+
declare const Revocation: {
|
|
20
|
+
/**
|
|
21
|
+
* Revokes a token, preventing it from being used even if not yet expired.
|
|
22
|
+
*
|
|
23
|
+
* @param storage - Storage adapter to use
|
|
24
|
+
* @param token - The token to revoke (access or refresh token)
|
|
25
|
+
* @param expiresAt - When the token naturally expires (milliseconds since epoch)
|
|
26
|
+
* @returns Promise that resolves when revocation is stored
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* // Revoke a refresh token on logout
|
|
31
|
+
* await Revocation.revoke(storage, refreshToken, expiresAt)
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
readonly revoke: (storage: StorageAdapter, token: string, expiresAt: number) => Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a token has been revoked.
|
|
37
|
+
* Returns false if token is not in revocation list (never revoked or already expired).
|
|
38
|
+
*
|
|
39
|
+
* @param storage - Storage adapter to use
|
|
40
|
+
* @param token - The token to check
|
|
41
|
+
* @returns Promise resolving to true if token is revoked, false otherwise
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* // Check if token was revoked before using it
|
|
46
|
+
* const isRevoked = await Revocation.isRevoked(storage, accessToken)
|
|
47
|
+
* if (isRevoked) {
|
|
48
|
+
* throw new InvalidAccessTokenError()
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
readonly isRevoked: (storage: StorageAdapter, token: string) => Promise<boolean>;
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { Revocation, RevocationRecord };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Storage } from "./storage/storage.mjs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
//#region src/revocation.ts
|
|
5
|
+
/**
|
|
6
|
+
* Token revocation management for Draft Auth.
|
|
7
|
+
* Handles blacklisting of revoked tokens to prevent their use.
|
|
8
|
+
*
|
|
9
|
+
* ## Overview
|
|
10
|
+
*
|
|
11
|
+
* Revocation allows users to invalidate specific tokens before their natural expiration.
|
|
12
|
+
* This is essential for logout functionality and security in case of token compromise.
|
|
13
|
+
*
|
|
14
|
+
* ## Storage Structure
|
|
15
|
+
*
|
|
16
|
+
* Revoked tokens are stored with their expiration time to allow automatic cleanup:
|
|
17
|
+
* ```
|
|
18
|
+
* revocation:token:{tokenHash} → { revokedAt: timestamp, expiresAt: timestamp }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ## Security Considerations
|
|
22
|
+
*
|
|
23
|
+
* - Revoked tokens are checked on every use
|
|
24
|
+
* - Storage automatically cleans up expired revocations
|
|
25
|
+
* - Hash tokens for storage to reduce memory usage
|
|
26
|
+
* - Use constant-time comparison for hash verification
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Hashes a token for storage.
|
|
32
|
+
* Uses SHA-256 to reduce storage size and prevent exposure of full token value.
|
|
33
|
+
*
|
|
34
|
+
* @param token - The token to hash
|
|
35
|
+
* @returns SHA-256 hash of the token
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
const hashToken = (token) => {
|
|
40
|
+
return createHash("sha256").update(token).digest("hex");
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Token revocation manager.
|
|
44
|
+
* Provides methods to revoke tokens and check if a token has been revoked.
|
|
45
|
+
*/
|
|
46
|
+
const Revocation = {
|
|
47
|
+
revoke: async (storage, token, expiresAt) => {
|
|
48
|
+
const key = ["revocation:token", hashToken(token)];
|
|
49
|
+
const record = {
|
|
50
|
+
revokedAt: Date.now(),
|
|
51
|
+
expiresAt
|
|
52
|
+
};
|
|
53
|
+
const ttlSeconds = Math.ceil((expiresAt - Date.now()) / 1e3);
|
|
54
|
+
await Storage.set(storage, key, record, Math.max(1, ttlSeconds));
|
|
55
|
+
},
|
|
56
|
+
isRevoked: async (storage, token) => {
|
|
57
|
+
const key = ["revocation:token", hashToken(token)];
|
|
58
|
+
return !!await Storage.get(storage, key);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
export { Revocation };
|