handshake-auth 0.1.0 → 0.2.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/ReadMe.md +230 -16
- package/dist/index.d.ts +18 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/express.d.ts +67 -0
- package/dist/middleware/express.d.ts.map +1 -1
- package/dist/middleware/express.js +69 -0
- package/dist/middleware/express.js.map +1 -1
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/index.js.map +1 -1
- package/dist/strategies/discord.d.ts +99 -0
- package/dist/strategies/discord.d.ts.map +1 -0
- package/dist/strategies/discord.js +85 -0
- package/dist/strategies/discord.js.map +1 -0
- package/dist/strategies/github.d.ts +112 -0
- package/dist/strategies/github.d.ts.map +1 -0
- package/dist/strategies/github.js +110 -0
- package/dist/strategies/github.js.map +1 -0
- package/dist/strategies/google.d.ts +91 -0
- package/dist/strategies/google.d.ts.map +1 -0
- package/dist/strategies/google.js +77 -0
- package/dist/strategies/google.js.map +1 -0
- package/dist/strategies/index.d.ts +16 -0
- package/dist/strategies/index.d.ts.map +1 -1
- package/dist/strategies/index.js +10 -0
- package/dist/strategies/index.js.map +1 -1
- package/dist/strategies/magic-link.d.ts +141 -0
- package/dist/strategies/magic-link.d.ts.map +1 -0
- package/dist/strategies/magic-link.js +186 -0
- package/dist/strategies/magic-link.js.map +1 -0
- package/dist/strategies/microsoft.d.ts +127 -0
- package/dist/strategies/microsoft.d.ts.map +1 -0
- package/dist/strategies/microsoft.js +98 -0
- package/dist/strategies/microsoft.js.map +1 -0
- package/dist/strategies/oauth-base.d.ts +162 -0
- package/dist/strategies/oauth-base.d.ts.map +1 -0
- package/dist/strategies/oauth-base.js +243 -0
- package/dist/strategies/oauth-base.js.map +1 -0
- package/dist/strategies/password.d.ts +69 -6
- package/dist/strategies/password.d.ts.map +1 -1
- package/dist/strategies/password.js +73 -24
- package/dist/strategies/password.js.map +1 -1
- package/dist/strategies/twitter-x.d.ts +130 -0
- package/dist/strategies/twitter-x.d.ts.map +1 -0
- package/dist/strategies/twitter-x.js +275 -0
- package/dist/strategies/twitter-x.js.map +1 -0
- package/dist/strategies/username-password.d.ts +38 -0
- package/dist/strategies/username-password.d.ts.map +1 -0
- package/dist/strategies/username-password.js +61 -0
- package/dist/strategies/username-password.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { OAuthStrategy } from './oauth-base.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discord OAuth2 authentication strategy.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const hs = new Handshake({
|
|
8
|
+
* findAccount: async (email) => db.accounts.findByEmail(email),
|
|
9
|
+
* findOrCreateFromOAuth: async (provider, profile) => {
|
|
10
|
+
* let account = await db.accounts.findByProviderId(provider, profile.id);
|
|
11
|
+
* if (!account) {
|
|
12
|
+
* account = await db.accounts.create({
|
|
13
|
+
* email: profile.email,
|
|
14
|
+
* name: profile.name,
|
|
15
|
+
* providerId: profile.id,
|
|
16
|
+
* provider,
|
|
17
|
+
* });
|
|
18
|
+
* }
|
|
19
|
+
* return account;
|
|
20
|
+
* },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* hs.use(new DiscordStrategy({
|
|
24
|
+
* clientId: process.env.DISCORD_CLIENT_ID!,
|
|
25
|
+
* clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
|
26
|
+
* redirectUri: 'http://localhost:3000/auth/discord/callback',
|
|
27
|
+
* }));
|
|
28
|
+
*
|
|
29
|
+
* // Routes
|
|
30
|
+
* app.get('/auth/discord', async (req, res) => {
|
|
31
|
+
* const result = await hs.authenticate('discord', req, res, 'redirect');
|
|
32
|
+
* if ('redirectUrl' in result) {
|
|
33
|
+
* res.redirect(result.redirectUrl);
|
|
34
|
+
* }
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* app.get('/auth/discord/callback', async (req, res) => {
|
|
38
|
+
* const result = await hs.authenticate('discord', req, res, 'callback');
|
|
39
|
+
* if (result.account) {
|
|
40
|
+
* login(req, result.account);
|
|
41
|
+
* res.redirect('/dashboard');
|
|
42
|
+
* } else {
|
|
43
|
+
* res.status(401).send(result.error);
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export class DiscordStrategy extends OAuthStrategy {
|
|
49
|
+
constructor(options) {
|
|
50
|
+
super({
|
|
51
|
+
name: 'discord',
|
|
52
|
+
clientId: options.clientId,
|
|
53
|
+
clientSecret: options.clientSecret,
|
|
54
|
+
redirectUri: options.redirectUri,
|
|
55
|
+
authorizeUrl: 'https://discord.com/api/oauth2/authorize',
|
|
56
|
+
tokenUrl: 'https://discord.com/api/oauth2/token',
|
|
57
|
+
userInfoUrl: 'https://discord.com/api/users/@me',
|
|
58
|
+
scopes: options.scopes ?? ['identify', 'email'],
|
|
59
|
+
stateCookieName: 'discord_oauth_state',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Map Discord profile to standard OAuthProfile.
|
|
64
|
+
*/
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
66
|
+
mapProfile(data, accessToken) {
|
|
67
|
+
const profile = data;
|
|
68
|
+
// Build avatar URL if avatar hash exists
|
|
69
|
+
let picture = null;
|
|
70
|
+
if (profile.avatar) {
|
|
71
|
+
const ext = profile.avatar.startsWith('a_') ? 'gif' : 'png';
|
|
72
|
+
picture = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${ext}`;
|
|
73
|
+
}
|
|
74
|
+
// Use global_name (display name) or fall back to username
|
|
75
|
+
const name = profile.global_name ?? profile.username;
|
|
76
|
+
return {
|
|
77
|
+
id: profile.id,
|
|
78
|
+
email: profile.email ?? null,
|
|
79
|
+
name,
|
|
80
|
+
picture,
|
|
81
|
+
raw: profile,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=discord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.js","sourceRoot":"","sources":["../../src/strategies/discord.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAkDhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,MAAM,OAAO,eAA0B,SAAQ,aAAuB;IACpE,YAAY,OAA+B;QACzC,KAAK,CAAC;YACJ,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,YAAY,EAAE,0CAA0C;YACxD,QAAQ,EAAE,sCAAsC;YAChD,WAAW,EAAE,mCAAmC;YAChD,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC;YAC/C,eAAe,EAAE,qBAAqB;SACvC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,6DAA6D;IACnD,UAAU,CAAC,IAAa,EAAE,WAAmB;QACrD,MAAM,OAAO,GAAG,IAAsB,CAAC;QAEvC,yCAAyC;QACzC,IAAI,OAAO,GAAkB,IAAI,CAAC;QAClC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;YAC5D,OAAO,GAAG,sCAAsC,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QACxF,CAAC;QAED,0DAA0D;QAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC;QAErD,OAAO;YACL,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;YAC5B,IAAI;YACJ,OAAO;YACP,GAAG,EAAE,OAA6C;SACnD,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { OAuthProfile } from '../types.js';
|
|
2
|
+
import { OAuthStrategy } from './oauth-base.js';
|
|
3
|
+
/**
|
|
4
|
+
* GitHub user profile from the /user endpoint.
|
|
5
|
+
*/
|
|
6
|
+
export interface GitHubProfile {
|
|
7
|
+
id: number;
|
|
8
|
+
login: string;
|
|
9
|
+
name?: string | null;
|
|
10
|
+
email?: string | null;
|
|
11
|
+
avatar_url?: string;
|
|
12
|
+
bio?: string | null;
|
|
13
|
+
company?: string | null;
|
|
14
|
+
location?: string | null;
|
|
15
|
+
blog?: string | null;
|
|
16
|
+
twitter_username?: string | null;
|
|
17
|
+
html_url?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* GitHub email object from the /user/emails endpoint.
|
|
21
|
+
*/
|
|
22
|
+
export interface GitHubEmail {
|
|
23
|
+
email: string;
|
|
24
|
+
primary: boolean;
|
|
25
|
+
verified: boolean;
|
|
26
|
+
visibility: string | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration options for GitHub OAuth strategy.
|
|
30
|
+
*/
|
|
31
|
+
export interface GitHubStrategyOptions {
|
|
32
|
+
/**
|
|
33
|
+
* GitHub OAuth client ID
|
|
34
|
+
*/
|
|
35
|
+
clientId: string;
|
|
36
|
+
/**
|
|
37
|
+
* GitHub OAuth client secret
|
|
38
|
+
*/
|
|
39
|
+
clientSecret: string;
|
|
40
|
+
/**
|
|
41
|
+
* Your callback URL (must match GitHub OAuth App configuration)
|
|
42
|
+
*/
|
|
43
|
+
redirectUri: string;
|
|
44
|
+
/**
|
|
45
|
+
* OAuth scopes to request
|
|
46
|
+
* @default ['read:user', 'user:email']
|
|
47
|
+
*/
|
|
48
|
+
scopes?: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* GitHub OAuth2 authentication strategy.
|
|
52
|
+
*
|
|
53
|
+
* Note: GitHub may not include the user's email in the profile response.
|
|
54
|
+
* This strategy automatically fetches the primary email from the /user/emails
|
|
55
|
+
* endpoint when needed.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const hs = new Handshake({
|
|
60
|
+
* findAccount: async (email) => db.accounts.findByEmail(email),
|
|
61
|
+
* findOrCreateFromOAuth: async (provider, profile) => {
|
|
62
|
+
* let account = await db.accounts.findByProviderId(provider, profile.id);
|
|
63
|
+
* if (!account) {
|
|
64
|
+
* account = await db.accounts.create({
|
|
65
|
+
* email: profile.email,
|
|
66
|
+
* name: profile.name,
|
|
67
|
+
* providerId: profile.id,
|
|
68
|
+
* provider,
|
|
69
|
+
* });
|
|
70
|
+
* }
|
|
71
|
+
* return account;
|
|
72
|
+
* },
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* hs.use(new GitHubStrategy({
|
|
76
|
+
* clientId: process.env.GITHUB_CLIENT_ID!,
|
|
77
|
+
* clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
78
|
+
* redirectUri: 'http://localhost:3000/auth/github/callback',
|
|
79
|
+
* }));
|
|
80
|
+
*
|
|
81
|
+
* // Routes
|
|
82
|
+
* app.get('/auth/github', async (req, res) => {
|
|
83
|
+
* const result = await hs.authenticate('github', req, res, 'redirect');
|
|
84
|
+
* if ('redirectUrl' in result) {
|
|
85
|
+
* res.redirect(result.redirectUrl);
|
|
86
|
+
* }
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* app.get('/auth/github/callback', async (req, res) => {
|
|
90
|
+
* const result = await hs.authenticate('github', req, res, 'callback');
|
|
91
|
+
* if (result.account) {
|
|
92
|
+
* login(req, result.account);
|
|
93
|
+
* res.redirect('/dashboard');
|
|
94
|
+
* } else {
|
|
95
|
+
* res.status(401).send(result.error);
|
|
96
|
+
* }
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export declare class GitHubStrategy<TAccount> extends OAuthStrategy<TAccount> {
|
|
101
|
+
constructor(options: GitHubStrategyOptions);
|
|
102
|
+
/**
|
|
103
|
+
* Map GitHub profile to standard OAuthProfile.
|
|
104
|
+
* Fetches email from /user/emails if not in profile.
|
|
105
|
+
*/
|
|
106
|
+
protected mapProfile(data: unknown, accessToken: string): Promise<OAuthProfile>;
|
|
107
|
+
/**
|
|
108
|
+
* Fetch the user's primary email from GitHub's /user/emails endpoint.
|
|
109
|
+
*/
|
|
110
|
+
private fetchPrimaryEmail;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/strategies/github.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,qBAAa,cAAc,CAAC,QAAQ,CAAE,SAAQ,aAAa,CAAC,QAAQ,CAAC;gBACvD,OAAO,EAAE,qBAAqB;IAc1C;;;OAGG;cACa,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAmBrF;;OAEG;YACW,iBAAiB;CAqBhC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { OAuthStrategy } from './oauth-base.js';
|
|
2
|
+
/**
|
|
3
|
+
* GitHub OAuth2 authentication strategy.
|
|
4
|
+
*
|
|
5
|
+
* Note: GitHub may not include the user's email in the profile response.
|
|
6
|
+
* This strategy automatically fetches the primary email from the /user/emails
|
|
7
|
+
* endpoint when needed.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const hs = new Handshake({
|
|
12
|
+
* findAccount: async (email) => db.accounts.findByEmail(email),
|
|
13
|
+
* findOrCreateFromOAuth: async (provider, profile) => {
|
|
14
|
+
* let account = await db.accounts.findByProviderId(provider, profile.id);
|
|
15
|
+
* if (!account) {
|
|
16
|
+
* account = await db.accounts.create({
|
|
17
|
+
* email: profile.email,
|
|
18
|
+
* name: profile.name,
|
|
19
|
+
* providerId: profile.id,
|
|
20
|
+
* provider,
|
|
21
|
+
* });
|
|
22
|
+
* }
|
|
23
|
+
* return account;
|
|
24
|
+
* },
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* hs.use(new GitHubStrategy({
|
|
28
|
+
* clientId: process.env.GITHUB_CLIENT_ID!,
|
|
29
|
+
* clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
30
|
+
* redirectUri: 'http://localhost:3000/auth/github/callback',
|
|
31
|
+
* }));
|
|
32
|
+
*
|
|
33
|
+
* // Routes
|
|
34
|
+
* app.get('/auth/github', async (req, res) => {
|
|
35
|
+
* const result = await hs.authenticate('github', req, res, 'redirect');
|
|
36
|
+
* if ('redirectUrl' in result) {
|
|
37
|
+
* res.redirect(result.redirectUrl);
|
|
38
|
+
* }
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* app.get('/auth/github/callback', async (req, res) => {
|
|
42
|
+
* const result = await hs.authenticate('github', req, res, 'callback');
|
|
43
|
+
* if (result.account) {
|
|
44
|
+
* login(req, result.account);
|
|
45
|
+
* res.redirect('/dashboard');
|
|
46
|
+
* } else {
|
|
47
|
+
* res.status(401).send(result.error);
|
|
48
|
+
* }
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export class GitHubStrategy extends OAuthStrategy {
|
|
53
|
+
constructor(options) {
|
|
54
|
+
super({
|
|
55
|
+
name: 'github',
|
|
56
|
+
clientId: options.clientId,
|
|
57
|
+
clientSecret: options.clientSecret,
|
|
58
|
+
redirectUri: options.redirectUri,
|
|
59
|
+
authorizeUrl: 'https://github.com/login/oauth/authorize',
|
|
60
|
+
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
61
|
+
userInfoUrl: 'https://api.github.com/user',
|
|
62
|
+
scopes: options.scopes ?? ['read:user', 'user:email'],
|
|
63
|
+
stateCookieName: 'github_oauth_state',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Map GitHub profile to standard OAuthProfile.
|
|
68
|
+
* Fetches email from /user/emails if not in profile.
|
|
69
|
+
*/
|
|
70
|
+
async mapProfile(data, accessToken) {
|
|
71
|
+
const profile = data;
|
|
72
|
+
// GitHub may not include email in the profile
|
|
73
|
+
// Fetch from /user/emails endpoint if needed
|
|
74
|
+
let email = profile.email;
|
|
75
|
+
if (!email) {
|
|
76
|
+
email = await this.fetchPrimaryEmail(accessToken);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
id: String(profile.id),
|
|
80
|
+
email: email ?? null,
|
|
81
|
+
name: profile.name ?? profile.login,
|
|
82
|
+
picture: profile.avatar_url ?? null,
|
|
83
|
+
raw: profile,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Fetch the user's primary email from GitHub's /user/emails endpoint.
|
|
88
|
+
*/
|
|
89
|
+
async fetchPrimaryEmail(accessToken) {
|
|
90
|
+
try {
|
|
91
|
+
const response = await fetch('https://api.github.com/user/emails', {
|
|
92
|
+
headers: {
|
|
93
|
+
Authorization: `Bearer ${accessToken}`,
|
|
94
|
+
Accept: 'application/json',
|
|
95
|
+
'User-Agent': 'handshake-auth',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const emails = (await response.json());
|
|
102
|
+
const primary = emails.find((e) => e.primary && e.verified);
|
|
103
|
+
return primary?.email ?? emails.find((e) => e.verified)?.email ?? null;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/strategies/github.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAuDhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,OAAO,cAAyB,SAAQ,aAAuB;IACnE,YAAY,OAA8B;QACxC,KAAK,CAAC;YACJ,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,YAAY,EAAE,0CAA0C;YACxD,QAAQ,EAAE,6CAA6C;YACvD,WAAW,EAAE,6BAA6B;YAC1C,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC;YACrD,eAAe,EAAE,oBAAoB;SACtC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACO,KAAK,CAAC,UAAU,CAAC,IAAa,EAAE,WAAmB;QAC3D,MAAM,OAAO,GAAG,IAAqB,CAAC;QAEtC,8CAA8C;QAC9C,6CAA6C;QAC7C,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACpD,CAAC;QAED,OAAO;YACL,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YACtB,KAAK,EAAE,KAAK,IAAI,IAAI;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK;YACnC,OAAO,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;YACnC,GAAG,EAAE,OAA6C;SACnD,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB,CAAC,WAAmB;QACjD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,oCAAoC,EAAE;gBACjE,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,WAAW,EAAE;oBACtC,MAAM,EAAE,kBAAkB;oBAC1B,YAAY,EAAE,gBAAgB;iBAC/B;aACF,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAkB,CAAC;YACxD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;YAC5D,OAAO,OAAO,EAAE,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC;QACzE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { OAuthProfile } from '../types.js';
|
|
2
|
+
import { OAuthStrategy } from './oauth-base.js';
|
|
3
|
+
/**
|
|
4
|
+
* Google user profile from the userinfo endpoint.
|
|
5
|
+
*/
|
|
6
|
+
export interface GoogleProfile {
|
|
7
|
+
sub: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
given_name?: string;
|
|
10
|
+
family_name?: string;
|
|
11
|
+
picture?: string;
|
|
12
|
+
email?: string;
|
|
13
|
+
email_verified?: boolean;
|
|
14
|
+
locale?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Configuration options for Google OAuth strategy.
|
|
18
|
+
*/
|
|
19
|
+
export interface GoogleStrategyOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Google OAuth client ID
|
|
22
|
+
*/
|
|
23
|
+
clientId: string;
|
|
24
|
+
/**
|
|
25
|
+
* Google OAuth client secret
|
|
26
|
+
*/
|
|
27
|
+
clientSecret: string;
|
|
28
|
+
/**
|
|
29
|
+
* Your callback URL (must match Google Cloud Console configuration)
|
|
30
|
+
*/
|
|
31
|
+
redirectUri: string;
|
|
32
|
+
/**
|
|
33
|
+
* OAuth scopes to request
|
|
34
|
+
* @default ['openid', 'email', 'profile']
|
|
35
|
+
*/
|
|
36
|
+
scopes?: string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Google OAuth2 authentication strategy using OpenID Connect.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const hs = new Handshake({
|
|
44
|
+
* findAccount: async (email) => db.accounts.findByEmail(email),
|
|
45
|
+
* findOrCreateFromOAuth: async (provider, profile) => {
|
|
46
|
+
* let account = await db.accounts.findByProviderId(provider, profile.id);
|
|
47
|
+
* if (!account) {
|
|
48
|
+
* account = await db.accounts.create({
|
|
49
|
+
* email: profile.email,
|
|
50
|
+
* name: profile.name,
|
|
51
|
+
* providerId: profile.id,
|
|
52
|
+
* provider,
|
|
53
|
+
* });
|
|
54
|
+
* }
|
|
55
|
+
* return account;
|
|
56
|
+
* },
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* hs.use(new GoogleStrategy({
|
|
60
|
+
* clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
61
|
+
* clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
62
|
+
* redirectUri: 'http://localhost:3000/auth/google/callback',
|
|
63
|
+
* }));
|
|
64
|
+
*
|
|
65
|
+
* // Routes
|
|
66
|
+
* app.get('/auth/google', async (req, res) => {
|
|
67
|
+
* const result = await hs.authenticate('google', req, res, 'redirect');
|
|
68
|
+
* if ('redirectUrl' in result) {
|
|
69
|
+
* res.redirect(result.redirectUrl);
|
|
70
|
+
* }
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* app.get('/auth/google/callback', async (req, res) => {
|
|
74
|
+
* const result = await hs.authenticate('google', req, res, 'callback');
|
|
75
|
+
* if (result.account) {
|
|
76
|
+
* login(req, result.account);
|
|
77
|
+
* res.redirect('/dashboard');
|
|
78
|
+
* } else {
|
|
79
|
+
* res.status(401).send(result.error);
|
|
80
|
+
* }
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare class GoogleStrategy<TAccount> extends OAuthStrategy<TAccount> {
|
|
85
|
+
constructor(options: GoogleStrategyOptions);
|
|
86
|
+
/**
|
|
87
|
+
* Map Google profile to standard OAuthProfile.
|
|
88
|
+
*/
|
|
89
|
+
protected mapProfile(data: unknown, accessToken: string): OAuthProfile;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=google.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/strategies/google.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,qBAAa,cAAc,CAAC,QAAQ,CAAE,SAAQ,aAAa,CAAC,QAAQ,CAAC;gBACvD,OAAO,EAAE,qBAAqB;IAc1C;;OAEG;IAEH,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,YAAY;CAUvE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { OAuthStrategy } from './oauth-base.js';
|
|
2
|
+
/**
|
|
3
|
+
* Google OAuth2 authentication strategy using OpenID Connect.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const hs = new Handshake({
|
|
8
|
+
* findAccount: async (email) => db.accounts.findByEmail(email),
|
|
9
|
+
* findOrCreateFromOAuth: async (provider, profile) => {
|
|
10
|
+
* let account = await db.accounts.findByProviderId(provider, profile.id);
|
|
11
|
+
* if (!account) {
|
|
12
|
+
* account = await db.accounts.create({
|
|
13
|
+
* email: profile.email,
|
|
14
|
+
* name: profile.name,
|
|
15
|
+
* providerId: profile.id,
|
|
16
|
+
* provider,
|
|
17
|
+
* });
|
|
18
|
+
* }
|
|
19
|
+
* return account;
|
|
20
|
+
* },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* hs.use(new GoogleStrategy({
|
|
24
|
+
* clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
25
|
+
* clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
26
|
+
* redirectUri: 'http://localhost:3000/auth/google/callback',
|
|
27
|
+
* }));
|
|
28
|
+
*
|
|
29
|
+
* // Routes
|
|
30
|
+
* app.get('/auth/google', async (req, res) => {
|
|
31
|
+
* const result = await hs.authenticate('google', req, res, 'redirect');
|
|
32
|
+
* if ('redirectUrl' in result) {
|
|
33
|
+
* res.redirect(result.redirectUrl);
|
|
34
|
+
* }
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* app.get('/auth/google/callback', async (req, res) => {
|
|
38
|
+
* const result = await hs.authenticate('google', req, res, 'callback');
|
|
39
|
+
* if (result.account) {
|
|
40
|
+
* login(req, result.account);
|
|
41
|
+
* res.redirect('/dashboard');
|
|
42
|
+
* } else {
|
|
43
|
+
* res.status(401).send(result.error);
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export class GoogleStrategy extends OAuthStrategy {
|
|
49
|
+
constructor(options) {
|
|
50
|
+
super({
|
|
51
|
+
name: 'google',
|
|
52
|
+
clientId: options.clientId,
|
|
53
|
+
clientSecret: options.clientSecret,
|
|
54
|
+
redirectUri: options.redirectUri,
|
|
55
|
+
authorizeUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
56
|
+
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
57
|
+
userInfoUrl: 'https://openidconnect.googleapis.com/v1/userinfo',
|
|
58
|
+
scopes: options.scopes ?? ['openid', 'email', 'profile'],
|
|
59
|
+
stateCookieName: 'google_oauth_state',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Map Google profile to standard OAuthProfile.
|
|
64
|
+
*/
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
66
|
+
mapProfile(data, accessToken) {
|
|
67
|
+
const profile = data;
|
|
68
|
+
return {
|
|
69
|
+
id: profile.sub,
|
|
70
|
+
email: profile.email ?? null,
|
|
71
|
+
name: profile.name ?? null,
|
|
72
|
+
picture: profile.picture ?? null,
|
|
73
|
+
raw: profile,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=google.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/strategies/google.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA0ChD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,MAAM,OAAO,cAAyB,SAAQ,aAAuB;IACnE,YAAY,OAA8B;QACxC,KAAK,CAAC;YACJ,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,YAAY,EAAE,8CAA8C;YAC5D,QAAQ,EAAE,qCAAqC;YAC/C,WAAW,EAAE,kDAAkD;YAC/D,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC;YACxD,eAAe,EAAE,oBAAoB;SACtC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,6DAA6D;IACnD,UAAU,CAAC,IAAa,EAAE,WAAmB;QACrD,MAAM,OAAO,GAAG,IAAqB,CAAC;QACtC,OAAO;YACL,EAAE,EAAE,OAAO,CAAC,GAAG;YACf,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;YAC5B,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI;YAC1B,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI;YAChC,GAAG,EAAE,OAA6C;SACnD,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -1,2 +1,18 @@
|
|
|
1
1
|
export { PasswordStrategy } from './password.js';
|
|
2
|
+
export type { PasswordStrategyOptions } from './password.js';
|
|
3
|
+
export { UsernamePasswordStrategy } from './username-password.js';
|
|
4
|
+
export { MagicLinkStrategy, MagicLinkSendStrategy, MagicLinkVerifyStrategy, useMagicLink, } from './magic-link.js';
|
|
5
|
+
export type { MagicLinkOptions, MagicLinkSendResult } from './magic-link.js';
|
|
6
|
+
export { OAuthStrategy, isOAuthRedirect } from './oauth-base.js';
|
|
7
|
+
export type { OAuthConfig, OAuthRedirectResult, OAuthTokenResponse, OAuthAuthResult, } from './oauth-base.js';
|
|
8
|
+
export { GoogleStrategy } from './google.js';
|
|
9
|
+
export type { GoogleProfile, GoogleStrategyOptions } from './google.js';
|
|
10
|
+
export { GitHubStrategy } from './github.js';
|
|
11
|
+
export type { GitHubProfile, GitHubEmail, GitHubStrategyOptions } from './github.js';
|
|
12
|
+
export { DiscordStrategy } from './discord.js';
|
|
13
|
+
export type { DiscordProfile, DiscordStrategyOptions } from './discord.js';
|
|
14
|
+
export { MicrosoftStrategy } from './microsoft.js';
|
|
15
|
+
export type { MicrosoftProfile, MicrosoftTenant, MicrosoftStrategyOptions } from './microsoft.js';
|
|
16
|
+
export { TwitterXStrategy } from './twitter-x.js';
|
|
17
|
+
export type { TwitterXProfile, TwitterXStrategyOptions } from './twitter-x.js';
|
|
2
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/strategies/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/strategies/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,YAAY,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG7E,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjE,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAErF,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAElG,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/strategies/index.js
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
export { PasswordStrategy } from './password.js';
|
|
2
|
+
export { UsernamePasswordStrategy } from './username-password.js';
|
|
3
|
+
export { MagicLinkStrategy, MagicLinkSendStrategy, MagicLinkVerifyStrategy, useMagicLink, } from './magic-link.js';
|
|
4
|
+
// OAuth base
|
|
5
|
+
export { OAuthStrategy, isOAuthRedirect } from './oauth-base.js';
|
|
6
|
+
// OAuth providers
|
|
7
|
+
export { GoogleStrategy } from './google.js';
|
|
8
|
+
export { GitHubStrategy } from './github.js';
|
|
9
|
+
export { DiscordStrategy } from './discord.js';
|
|
10
|
+
export { MicrosoftStrategy } from './microsoft.js';
|
|
11
|
+
export { TwitterXStrategy } from './twitter-x.js';
|
|
2
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/strategies/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/strategies/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,YAAY,GACb,MAAM,iBAAiB,CAAC;AAGzB,aAAa;AACb,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAQjE,kBAAkB;AAClB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|