@draftlab/auth 0.5.0 → 0.7.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/dist/ui/icon.d.mts +41 -4
- package/dist/ui/icon.mjs +196 -19
- package/dist/ui/select.mjs +27 -4
- package/package.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Provider } from "./provider.mjs";
|
|
2
|
+
import { Oauth2UserData, Oauth2WrappedConfig } from "./oauth2.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/provider/reddit.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration options for Reddit OAuth 2.0 provider.
|
|
8
|
+
* Extends the base OAuth 2.0 configuration with Reddit-specific documentation.
|
|
9
|
+
*/
|
|
10
|
+
interface RedditConfig extends Oauth2WrappedConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Reddit app client ID.
|
|
13
|
+
* Get this from your Reddit application preferences at https://www.reddit.com/prefs/apps
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* {
|
|
18
|
+
* clientID: "abcdef123456"
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
readonly clientID: string;
|
|
23
|
+
/**
|
|
24
|
+
* Reddit app client secret.
|
|
25
|
+
* Keep this secure and never expose it to client-side code.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* {
|
|
30
|
+
* clientSecret: process.env.REDDIT_CLIENT_SECRET
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
readonly clientSecret: string;
|
|
35
|
+
/**
|
|
36
|
+
* Reddit 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
|
+
* "identity", // Access user identity
|
|
44
|
+
* "read" // Read private data
|
|
45
|
+
* ]
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
readonly scopes: string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates a Reddit OAuth 2.0 authentication provider.
|
|
53
|
+
* Allows users to authenticate using their Reddit accounts.
|
|
54
|
+
*
|
|
55
|
+
* @param config - Reddit OAuth 2.0 configuration
|
|
56
|
+
* @returns OAuth 2.0 provider configured for Reddit
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* // Basic Reddit authentication
|
|
61
|
+
* const basicReddit = RedditProvider({
|
|
62
|
+
* clientID: process.env.REDDIT_CLIENT_ID,
|
|
63
|
+
* clientSecret: process.env.REDDIT_CLIENT_SECRET
|
|
64
|
+
* })
|
|
65
|
+
*
|
|
66
|
+
* // Reddit with identity scope
|
|
67
|
+
* const redditWithIdentity = RedditProvider({
|
|
68
|
+
* clientID: process.env.REDDIT_CLIENT_ID,
|
|
69
|
+
* clientSecret: process.env.REDDIT_CLIENT_SECRET,
|
|
70
|
+
* scopes: ["identity"]
|
|
71
|
+
* })
|
|
72
|
+
*
|
|
73
|
+
* // Using the access token to fetch user data
|
|
74
|
+
* export default issuer({
|
|
75
|
+
* providers: { reddit: redditWithIdentity },
|
|
76
|
+
* success: async (ctx, value) => {
|
|
77
|
+
* if (value.provider === "reddit") {
|
|
78
|
+
* const token = value.tokenset.access
|
|
79
|
+
*
|
|
80
|
+
* const userRes = await fetch('https://oauth.reddit.com/api/v1/me', {
|
|
81
|
+
* headers: {
|
|
82
|
+
* 'Authorization': `Bearer ${token}`,
|
|
83
|
+
* 'User-Agent': 'YourApp/1.0'
|
|
84
|
+
* }
|
|
85
|
+
* })
|
|
86
|
+
* const user = await userRes.json()
|
|
87
|
+
*
|
|
88
|
+
* return ctx.subject("user", {
|
|
89
|
+
* redditId: user.id,
|
|
90
|
+
* username: user.name,
|
|
91
|
+
* linkKarma: user.link_karma,
|
|
92
|
+
* commentKarma: user.comment_karma
|
|
93
|
+
* })
|
|
94
|
+
* }
|
|
95
|
+
* }
|
|
96
|
+
* })
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
declare const RedditProvider: (config: RedditConfig) => Provider<Oauth2UserData>;
|
|
100
|
+
//#endregion
|
|
101
|
+
export { RedditConfig, RedditProvider };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Oauth2Provider } from "./oauth2.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/provider/reddit.ts
|
|
4
|
+
/**
|
|
5
|
+
* Reddit authentication provider for Draft Auth.
|
|
6
|
+
* Implements OAuth 2.0 flow for authenticating users with their Reddit accounts.
|
|
7
|
+
*
|
|
8
|
+
* ## Quick Setup
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { RedditProvider } from "@draftlab/auth/provider/reddit"
|
|
12
|
+
*
|
|
13
|
+
* export default issuer({
|
|
14
|
+
* providers: {
|
|
15
|
+
* reddit: RedditProvider({
|
|
16
|
+
* clientID: process.env.REDDIT_CLIENT_ID,
|
|
17
|
+
* clientSecret: process.env.REDDIT_CLIENT_SECRET,
|
|
18
|
+
* scopes: ["identity"]
|
|
19
|
+
* })
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Common Scopes
|
|
25
|
+
*
|
|
26
|
+
* - `identity` - Access user's identity information
|
|
27
|
+
* - `read` - Access user's private data (saved posts, hidden posts, etc.)
|
|
28
|
+
* - `submit` - Submit links and posts
|
|
29
|
+
* - `modposts` - Moderate posts
|
|
30
|
+
* - `privatemessages` - Access private messages
|
|
31
|
+
* - `subscribe` - Subscribe to subreddits
|
|
32
|
+
* - `wikiread` - Read wiki pages
|
|
33
|
+
*
|
|
34
|
+
* ## User Data Access
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* success: async (ctx, value) => {
|
|
38
|
+
* if (value.provider === "reddit") {
|
|
39
|
+
* const accessToken = value.tokenset.access
|
|
40
|
+
*
|
|
41
|
+
* // Fetch user information
|
|
42
|
+
* const userResponse = await fetch('https://oauth.reddit.com/api/v1/me', {
|
|
43
|
+
* headers: { Authorization: `Bearer ${accessToken}` }
|
|
44
|
+
* })
|
|
45
|
+
* const user = await userResponse.json()
|
|
46
|
+
*
|
|
47
|
+
* // User info: id, name, created_utc, link_karma, comment_karma
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @packageDocumentation
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* Creates a Reddit OAuth 2.0 authentication provider.
|
|
56
|
+
* Allows users to authenticate using their Reddit accounts.
|
|
57
|
+
*
|
|
58
|
+
* @param config - Reddit OAuth 2.0 configuration
|
|
59
|
+
* @returns OAuth 2.0 provider configured for Reddit
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* // Basic Reddit authentication
|
|
64
|
+
* const basicReddit = RedditProvider({
|
|
65
|
+
* clientID: process.env.REDDIT_CLIENT_ID,
|
|
66
|
+
* clientSecret: process.env.REDDIT_CLIENT_SECRET
|
|
67
|
+
* })
|
|
68
|
+
*
|
|
69
|
+
* // Reddit with identity scope
|
|
70
|
+
* const redditWithIdentity = RedditProvider({
|
|
71
|
+
* clientID: process.env.REDDIT_CLIENT_ID,
|
|
72
|
+
* clientSecret: process.env.REDDIT_CLIENT_SECRET,
|
|
73
|
+
* scopes: ["identity"]
|
|
74
|
+
* })
|
|
75
|
+
*
|
|
76
|
+
* // Using the access token to fetch user data
|
|
77
|
+
* export default issuer({
|
|
78
|
+
* providers: { reddit: redditWithIdentity },
|
|
79
|
+
* success: async (ctx, value) => {
|
|
80
|
+
* if (value.provider === "reddit") {
|
|
81
|
+
* const token = value.tokenset.access
|
|
82
|
+
*
|
|
83
|
+
* const userRes = await fetch('https://oauth.reddit.com/api/v1/me', {
|
|
84
|
+
* headers: {
|
|
85
|
+
* 'Authorization': `Bearer ${token}`,
|
|
86
|
+
* 'User-Agent': 'YourApp/1.0'
|
|
87
|
+
* }
|
|
88
|
+
* })
|
|
89
|
+
* const user = await userRes.json()
|
|
90
|
+
*
|
|
91
|
+
* return ctx.subject("user", {
|
|
92
|
+
* redditId: user.id,
|
|
93
|
+
* username: user.name,
|
|
94
|
+
* linkKarma: user.link_karma,
|
|
95
|
+
* commentKarma: user.comment_karma
|
|
96
|
+
* })
|
|
97
|
+
* }
|
|
98
|
+
* }
|
|
99
|
+
* })
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
const RedditProvider = (config) => {
|
|
103
|
+
return Oauth2Provider({
|
|
104
|
+
...config,
|
|
105
|
+
type: "reddit",
|
|
106
|
+
endpoint: {
|
|
107
|
+
authorization: "https://www.reddit.com/api/v1/authorize",
|
|
108
|
+
token: "https://www.reddit.com/api/v1/access_token"
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
export { RedditProvider };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Provider } from "./provider.mjs";
|
|
2
|
+
import { Oauth2UserData, Oauth2WrappedConfig } from "./oauth2.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/provider/slack.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration options for Slack OAuth 2.0 provider.
|
|
8
|
+
* Extends the base OAuth 2.0 configuration with Slack-specific documentation.
|
|
9
|
+
*/
|
|
10
|
+
interface SlackConfig extends Oauth2WrappedConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Slack app client ID.
|
|
13
|
+
* Get this from your Slack App settings at https://api.slack.com/apps
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* {
|
|
18
|
+
* clientID: "123456789.1234567890"
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
readonly clientID: string;
|
|
23
|
+
/**
|
|
24
|
+
* Slack app client secret.
|
|
25
|
+
* Keep this secure and never expose it to client-side code.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* {
|
|
30
|
+
* clientSecret: process.env.SLACK_CLIENT_SECRET
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
readonly clientSecret: string;
|
|
35
|
+
/**
|
|
36
|
+
* Slack 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
|
+
* "users:read", // Access to user profiles
|
|
44
|
+
* "users:read.email", // Access user emails
|
|
45
|
+
* "team:read" // Access team information
|
|
46
|
+
* ]
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
readonly scopes: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates a Slack OAuth 2.0 authentication provider.
|
|
54
|
+
* Allows users to authenticate using their Slack accounts.
|
|
55
|
+
*
|
|
56
|
+
* @param config - Slack OAuth 2.0 configuration
|
|
57
|
+
* @returns OAuth 2.0 provider configured for Slack
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* // Basic Slack authentication
|
|
62
|
+
* const basicSlack = SlackProvider({
|
|
63
|
+
* clientID: process.env.SLACK_CLIENT_ID,
|
|
64
|
+
* clientSecret: process.env.SLACK_CLIENT_SECRET
|
|
65
|
+
* })
|
|
66
|
+
*
|
|
67
|
+
* // Slack with user scopes
|
|
68
|
+
* const slackWithScopes = SlackProvider({
|
|
69
|
+
* clientID: process.env.SLACK_CLIENT_ID,
|
|
70
|
+
* clientSecret: process.env.SLACK_CLIENT_SECRET,
|
|
71
|
+
* scopes: ["users:read", "users:read.email", "team:read"]
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* // Using the access token to fetch user data
|
|
75
|
+
* export default issuer({
|
|
76
|
+
* providers: { slack: slackWithScopes },
|
|
77
|
+
* success: async (ctx, value) => {
|
|
78
|
+
* if (value.provider === "slack") {
|
|
79
|
+
* const token = value.tokenset.access
|
|
80
|
+
*
|
|
81
|
+
* // Get basic user info
|
|
82
|
+
* const authRes = await fetch('https://slack.com/api/auth.test', {
|
|
83
|
+
* headers: { Authorization: `Bearer ${token}` }
|
|
84
|
+
* })
|
|
85
|
+
* const authInfo = await authRes.json()
|
|
86
|
+
*
|
|
87
|
+
* // Get detailed user info
|
|
88
|
+
* const userRes = await fetch(
|
|
89
|
+
* `https://slack.com/api/users.info?user=${authInfo.user_id}`,
|
|
90
|
+
* { headers: { Authorization: `Bearer ${token}` } }
|
|
91
|
+
* )
|
|
92
|
+
* const { user } = await userRes.json()
|
|
93
|
+
*
|
|
94
|
+
* return ctx.subject("user", {
|
|
95
|
+
* slackId: user.id,
|
|
96
|
+
* username: user.name,
|
|
97
|
+
* realName: user.real_name,
|
|
98
|
+
* email: user.profile?.email,
|
|
99
|
+
* workspace: authInfo.team_id
|
|
100
|
+
* })
|
|
101
|
+
* }
|
|
102
|
+
* }
|
|
103
|
+
* })
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
declare const SlackProvider: (config: SlackConfig) => Provider<Oauth2UserData>;
|
|
107
|
+
//#endregion
|
|
108
|
+
export { SlackConfig, SlackProvider };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Oauth2Provider } from "./oauth2.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/provider/slack.ts
|
|
4
|
+
/**
|
|
5
|
+
* Slack authentication provider for Draft Auth.
|
|
6
|
+
* Implements OAuth 2.0 flow for authenticating users with their Slack accounts.
|
|
7
|
+
*
|
|
8
|
+
* ## Quick Setup
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { SlackProvider } from "@draftlab/auth/provider/slack"
|
|
12
|
+
*
|
|
13
|
+
* export default issuer({
|
|
14
|
+
* providers: {
|
|
15
|
+
* slack: SlackProvider({
|
|
16
|
+
* clientID: process.env.SLACK_CLIENT_ID,
|
|
17
|
+
* clientSecret: process.env.SLACK_CLIENT_SECRET,
|
|
18
|
+
* scopes: ["users:read", "users:read.email"]
|
|
19
|
+
* })
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Common Scopes
|
|
25
|
+
*
|
|
26
|
+
* - `users:read` - Access to user profiles
|
|
27
|
+
* - `users:read.email` - Access user email addresses
|
|
28
|
+
* - `team:read` - Access team information
|
|
29
|
+
* - `channels:read` - View channels
|
|
30
|
+
* - `groups:read` - View private channels
|
|
31
|
+
* - `im:read` - View direct messages
|
|
32
|
+
* - `mpim:read` - View group direct messages
|
|
33
|
+
*
|
|
34
|
+
* ## User Data Access
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* success: async (ctx, value) => {
|
|
38
|
+
* if (value.provider === "slack") {
|
|
39
|
+
* const accessToken = value.tokenset.access
|
|
40
|
+
*
|
|
41
|
+
* // Fetch user information
|
|
42
|
+
* const userResponse = await fetch('https://slack.com/api/auth.test', {
|
|
43
|
+
* headers: { Authorization: `Bearer ${accessToken}` }
|
|
44
|
+
* })
|
|
45
|
+
* const userInfo = await userResponse.json()
|
|
46
|
+
*
|
|
47
|
+
* // Get user details
|
|
48
|
+
* const userDetailsResponse = await fetch(
|
|
49
|
+
* `https://slack.com/api/users.info?user=${userInfo.user_id}`,
|
|
50
|
+
* { headers: { Authorization: `Bearer ${accessToken}` } }
|
|
51
|
+
* )
|
|
52
|
+
* const { user } = await userDetailsResponse.json()
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @packageDocumentation
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Creates a Slack OAuth 2.0 authentication provider.
|
|
61
|
+
* Allows users to authenticate using their Slack accounts.
|
|
62
|
+
*
|
|
63
|
+
* @param config - Slack OAuth 2.0 configuration
|
|
64
|
+
* @returns OAuth 2.0 provider configured for Slack
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* // Basic Slack authentication
|
|
69
|
+
* const basicSlack = SlackProvider({
|
|
70
|
+
* clientID: process.env.SLACK_CLIENT_ID,
|
|
71
|
+
* clientSecret: process.env.SLACK_CLIENT_SECRET
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* // Slack with user scopes
|
|
75
|
+
* const slackWithScopes = SlackProvider({
|
|
76
|
+
* clientID: process.env.SLACK_CLIENT_ID,
|
|
77
|
+
* clientSecret: process.env.SLACK_CLIENT_SECRET,
|
|
78
|
+
* scopes: ["users:read", "users:read.email", "team:read"]
|
|
79
|
+
* })
|
|
80
|
+
*
|
|
81
|
+
* // Using the access token to fetch user data
|
|
82
|
+
* export default issuer({
|
|
83
|
+
* providers: { slack: slackWithScopes },
|
|
84
|
+
* success: async (ctx, value) => {
|
|
85
|
+
* if (value.provider === "slack") {
|
|
86
|
+
* const token = value.tokenset.access
|
|
87
|
+
*
|
|
88
|
+
* // Get basic user info
|
|
89
|
+
* const authRes = await fetch('https://slack.com/api/auth.test', {
|
|
90
|
+
* headers: { Authorization: `Bearer ${token}` }
|
|
91
|
+
* })
|
|
92
|
+
* const authInfo = await authRes.json()
|
|
93
|
+
*
|
|
94
|
+
* // Get detailed user info
|
|
95
|
+
* const userRes = await fetch(
|
|
96
|
+
* `https://slack.com/api/users.info?user=${authInfo.user_id}`,
|
|
97
|
+
* { headers: { Authorization: `Bearer ${token}` } }
|
|
98
|
+
* )
|
|
99
|
+
* const { user } = await userRes.json()
|
|
100
|
+
*
|
|
101
|
+
* return ctx.subject("user", {
|
|
102
|
+
* slackId: user.id,
|
|
103
|
+
* username: user.name,
|
|
104
|
+
* realName: user.real_name,
|
|
105
|
+
* email: user.profile?.email,
|
|
106
|
+
* workspace: authInfo.team_id
|
|
107
|
+
* })
|
|
108
|
+
* }
|
|
109
|
+
* }
|
|
110
|
+
* })
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
const SlackProvider = (config) => {
|
|
114
|
+
return Oauth2Provider({
|
|
115
|
+
...config,
|
|
116
|
+
type: "slack",
|
|
117
|
+
endpoint: {
|
|
118
|
+
authorization: "https://slack.com/oauth_authorize",
|
|
119
|
+
token: "https://slack.com/api/oauth.v2.access"
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { SlackProvider };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Provider } from "./provider.mjs";
|
|
2
|
+
import { Oauth2UserData, Oauth2WrappedConfig } from "./oauth2.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/provider/spotify.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration options for Spotify OAuth 2.0 provider.
|
|
8
|
+
* Extends the base OAuth 2.0 configuration with Spotify-specific documentation.
|
|
9
|
+
*/
|
|
10
|
+
interface SpotifyConfig extends Oauth2WrappedConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Spotify app client ID.
|
|
13
|
+
* Get this from your Spotify App at https://developer.spotify.com/dashboard
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* {
|
|
18
|
+
* clientID: "abcdef123456"
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
readonly clientID: string;
|
|
23
|
+
/**
|
|
24
|
+
* Spotify app client secret.
|
|
25
|
+
* Keep this secure and never expose it to client-side code.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* {
|
|
30
|
+
* clientSecret: process.env.SPOTIFY_CLIENT_SECRET
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
readonly clientSecret: string;
|
|
35
|
+
/**
|
|
36
|
+
* Spotify 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-private", // Access private user data
|
|
44
|
+
* "user-read-email", // Access user email
|
|
45
|
+
* "user-top-read" // Read top artists and tracks
|
|
46
|
+
* ]
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
readonly scopes: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates a Spotify OAuth 2.0 authentication provider.
|
|
54
|
+
* Allows users to authenticate using their Spotify accounts.
|
|
55
|
+
*
|
|
56
|
+
* @param config - Spotify OAuth 2.0 configuration
|
|
57
|
+
* @returns OAuth 2.0 provider configured for Spotify
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* // Basic Spotify authentication
|
|
62
|
+
* const basicSpotify = SpotifyProvider({
|
|
63
|
+
* clientID: process.env.SPOTIFY_CLIENT_ID,
|
|
64
|
+
* clientSecret: process.env.SPOTIFY_CLIENT_SECRET
|
|
65
|
+
* })
|
|
66
|
+
*
|
|
67
|
+
* // Spotify with user data access
|
|
68
|
+
* const spotifyWithScopes = SpotifyProvider({
|
|
69
|
+
* clientID: process.env.SPOTIFY_CLIENT_ID,
|
|
70
|
+
* clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
|
|
71
|
+
* scopes: ["user-read-private", "user-read-email", "user-top-read"]
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* // Using the access token to fetch user data
|
|
75
|
+
* export default issuer({
|
|
76
|
+
* providers: { spotify: spotifyWithScopes },
|
|
77
|
+
* success: async (ctx, value) => {
|
|
78
|
+
* if (value.provider === "spotify") {
|
|
79
|
+
* const token = value.tokenset.access
|
|
80
|
+
*
|
|
81
|
+
* const userRes = await fetch('https://api.spotify.com/v1/me', {
|
|
82
|
+
* headers: { Authorization: `Bearer ${token}` }
|
|
83
|
+
* })
|
|
84
|
+
* const user = await userRes.json()
|
|
85
|
+
*
|
|
86
|
+
* // Optionally fetch top tracks
|
|
87
|
+
* const topRes = await fetch('https://api.spotify.com/v1/me/top/tracks?limit=5', {
|
|
88
|
+
* headers: { Authorization: `Bearer ${token}` }
|
|
89
|
+
* })
|
|
90
|
+
* const { items: topTracks } = await topRes.json()
|
|
91
|
+
*
|
|
92
|
+
* return ctx.subject("user", {
|
|
93
|
+
* spotifyId: user.id,
|
|
94
|
+
* email: user.email,
|
|
95
|
+
* displayName: user.display_name,
|
|
96
|
+
* profileUrl: user.external_urls?.spotify,
|
|
97
|
+
* followers: user.followers?.total,
|
|
98
|
+
* topTracks: topTracks.map(t => t.name)
|
|
99
|
+
* })
|
|
100
|
+
* }
|
|
101
|
+
* }
|
|
102
|
+
* })
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
declare const SpotifyProvider: (config: SpotifyConfig) => Provider<Oauth2UserData>;
|
|
106
|
+
//#endregion
|
|
107
|
+
export { SpotifyConfig, SpotifyProvider };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Oauth2Provider } from "./oauth2.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/provider/spotify.ts
|
|
4
|
+
/**
|
|
5
|
+
* Spotify authentication provider for Draft Auth.
|
|
6
|
+
* Implements OAuth 2.0 flow for authenticating users with their Spotify accounts.
|
|
7
|
+
*
|
|
8
|
+
* ## Quick Setup
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { SpotifyProvider } from "@draftlab/auth/provider/spotify"
|
|
12
|
+
*
|
|
13
|
+
* export default issuer({
|
|
14
|
+
* providers: {
|
|
15
|
+
* spotify: SpotifyProvider({
|
|
16
|
+
* clientID: process.env.SPOTIFY_CLIENT_ID,
|
|
17
|
+
* clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
|
|
18
|
+
* scopes: ["user-read-private", "user-read-email"]
|
|
19
|
+
* })
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Common Scopes
|
|
25
|
+
*
|
|
26
|
+
* - `user-read-private` - Access user's private data
|
|
27
|
+
* - `user-read-email` - Access user's email address
|
|
28
|
+
* - `user-top-read` - Read user's top artists and tracks
|
|
29
|
+
* - `user-read-playback-state` - Read current playback state
|
|
30
|
+
* - `user-modify-playback-state` - Modify playback state
|
|
31
|
+
* - `user-read-currently-playing` - Read currently playing track
|
|
32
|
+
* - `playlist-read-private` - Access private playlists
|
|
33
|
+
* - `playlist-read-public` - Access public playlists
|
|
34
|
+
* - `user-library-read` - Read user's library
|
|
35
|
+
* - `user-follow-read` - Read followed artists and users
|
|
36
|
+
*
|
|
37
|
+
* ## User Data Access
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* success: async (ctx, value) => {
|
|
41
|
+
* if (value.provider === "spotify") {
|
|
42
|
+
* const accessToken = value.tokenset.access
|
|
43
|
+
*
|
|
44
|
+
* // Fetch user profile
|
|
45
|
+
* const userResponse = await fetch('https://api.spotify.com/v1/me', {
|
|
46
|
+
* headers: { Authorization: `Bearer ${accessToken}` }
|
|
47
|
+
* })
|
|
48
|
+
* const user = await userResponse.json()
|
|
49
|
+
*
|
|
50
|
+
* // User info: id, email, display_name, external_urls, images, followers
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @packageDocumentation
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Creates a Spotify OAuth 2.0 authentication provider.
|
|
59
|
+
* Allows users to authenticate using their Spotify accounts.
|
|
60
|
+
*
|
|
61
|
+
* @param config - Spotify OAuth 2.0 configuration
|
|
62
|
+
* @returns OAuth 2.0 provider configured for Spotify
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* // Basic Spotify authentication
|
|
67
|
+
* const basicSpotify = SpotifyProvider({
|
|
68
|
+
* clientID: process.env.SPOTIFY_CLIENT_ID,
|
|
69
|
+
* clientSecret: process.env.SPOTIFY_CLIENT_SECRET
|
|
70
|
+
* })
|
|
71
|
+
*
|
|
72
|
+
* // Spotify with user data access
|
|
73
|
+
* const spotifyWithScopes = SpotifyProvider({
|
|
74
|
+
* clientID: process.env.SPOTIFY_CLIENT_ID,
|
|
75
|
+
* clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
|
|
76
|
+
* scopes: ["user-read-private", "user-read-email", "user-top-read"]
|
|
77
|
+
* })
|
|
78
|
+
*
|
|
79
|
+
* // Using the access token to fetch user data
|
|
80
|
+
* export default issuer({
|
|
81
|
+
* providers: { spotify: spotifyWithScopes },
|
|
82
|
+
* success: async (ctx, value) => {
|
|
83
|
+
* if (value.provider === "spotify") {
|
|
84
|
+
* const token = value.tokenset.access
|
|
85
|
+
*
|
|
86
|
+
* const userRes = await fetch('https://api.spotify.com/v1/me', {
|
|
87
|
+
* headers: { Authorization: `Bearer ${token}` }
|
|
88
|
+
* })
|
|
89
|
+
* const user = await userRes.json()
|
|
90
|
+
*
|
|
91
|
+
* // Optionally fetch top tracks
|
|
92
|
+
* const topRes = await fetch('https://api.spotify.com/v1/me/top/tracks?limit=5', {
|
|
93
|
+
* headers: { Authorization: `Bearer ${token}` }
|
|
94
|
+
* })
|
|
95
|
+
* const { items: topTracks } = await topRes.json()
|
|
96
|
+
*
|
|
97
|
+
* return ctx.subject("user", {
|
|
98
|
+
* spotifyId: user.id,
|
|
99
|
+
* email: user.email,
|
|
100
|
+
* displayName: user.display_name,
|
|
101
|
+
* profileUrl: user.external_urls?.spotify,
|
|
102
|
+
* followers: user.followers?.total,
|
|
103
|
+
* topTracks: topTracks.map(t => t.name)
|
|
104
|
+
* })
|
|
105
|
+
* }
|
|
106
|
+
* }
|
|
107
|
+
* })
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
const SpotifyProvider = (config) => {
|
|
111
|
+
return Oauth2Provider({
|
|
112
|
+
...config,
|
|
113
|
+
type: "spotify",
|
|
114
|
+
endpoint: {
|
|
115
|
+
authorization: "https://accounts.spotify.com/authorize",
|
|
116
|
+
token: "https://accounts.spotify.com/api/token"
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
export { SpotifyProvider };
|