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,130 @@
|
|
|
1
|
+
import type { OAuthProfile, AuthResult, HandshakeCallbacks } from '../types.js';
|
|
2
|
+
import { OAuthStrategy } from './oauth-base.js';
|
|
3
|
+
/**
|
|
4
|
+
* Twitter/X user profile from the /2/users/me endpoint.
|
|
5
|
+
*/
|
|
6
|
+
export interface TwitterXProfile {
|
|
7
|
+
data: {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
username: string;
|
|
11
|
+
profile_image_url?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
location?: string;
|
|
14
|
+
url?: string;
|
|
15
|
+
verified?: boolean;
|
|
16
|
+
created_at?: string;
|
|
17
|
+
public_metrics?: {
|
|
18
|
+
followers_count: number;
|
|
19
|
+
following_count: number;
|
|
20
|
+
tweet_count: number;
|
|
21
|
+
listed_count: number;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Configuration options for Twitter/X OAuth strategy.
|
|
27
|
+
*/
|
|
28
|
+
export interface TwitterXStrategyOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Twitter/X OAuth 2.0 client ID
|
|
31
|
+
*/
|
|
32
|
+
clientId: string;
|
|
33
|
+
/**
|
|
34
|
+
* Twitter/X OAuth 2.0 client secret
|
|
35
|
+
*/
|
|
36
|
+
clientSecret: string;
|
|
37
|
+
/**
|
|
38
|
+
* Your callback URL (must match Twitter Developer Portal configuration)
|
|
39
|
+
*/
|
|
40
|
+
redirectUri: string;
|
|
41
|
+
/**
|
|
42
|
+
* OAuth scopes to request
|
|
43
|
+
* @default ['tweet.read', 'users.read']
|
|
44
|
+
*/
|
|
45
|
+
scopes?: string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Twitter/X OAuth 2.0 authentication strategy.
|
|
49
|
+
*
|
|
50
|
+
* Uses OAuth 2.0 with PKCE (Proof Key for Code Exchange) as required by
|
|
51
|
+
* Twitter's API v2.
|
|
52
|
+
*
|
|
53
|
+
* Note: Twitter does not provide email addresses through the OAuth API.
|
|
54
|
+
* The profile will have email set to null.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const hs = new Handshake({
|
|
59
|
+
* findAccount: async (email) => db.accounts.findByEmail(email),
|
|
60
|
+
* findOrCreateFromOAuth: async (provider, profile) => {
|
|
61
|
+
* // Note: Twitter doesn't provide email, use profile.id for lookup
|
|
62
|
+
* let account = await db.accounts.findByProviderId(provider, profile.id);
|
|
63
|
+
* if (!account) {
|
|
64
|
+
* account = await db.accounts.create({
|
|
65
|
+
* name: profile.name,
|
|
66
|
+
* providerId: profile.id,
|
|
67
|
+
* provider,
|
|
68
|
+
* // You may want to prompt user for email separately
|
|
69
|
+
* });
|
|
70
|
+
* }
|
|
71
|
+
* return account;
|
|
72
|
+
* },
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* hs.use(new TwitterXStrategy({
|
|
76
|
+
* clientId: process.env.TWITTER_CLIENT_ID!,
|
|
77
|
+
* clientSecret: process.env.TWITTER_CLIENT_SECRET!,
|
|
78
|
+
* redirectUri: 'http://localhost:3000/auth/twitter/callback',
|
|
79
|
+
* }));
|
|
80
|
+
*
|
|
81
|
+
* // Routes
|
|
82
|
+
* app.get('/auth/twitter', async (req, res) => {
|
|
83
|
+
* const result = await hs.authenticate('twitter-x', req, res, 'redirect');
|
|
84
|
+
* if ('redirectUrl' in result) {
|
|
85
|
+
* res.redirect(result.redirectUrl);
|
|
86
|
+
* }
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* app.get('/auth/twitter/callback', async (req, res) => {
|
|
90
|
+
* const result = await hs.authenticate('twitter-x', 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 TwitterXStrategy<TAccount> extends OAuthStrategy<TAccount> {
|
|
101
|
+
private readonly codeVerifierCookieName;
|
|
102
|
+
constructor(options: TwitterXStrategyOptions);
|
|
103
|
+
/**
|
|
104
|
+
* Override authenticate to handle PKCE flow.
|
|
105
|
+
*/
|
|
106
|
+
authenticate(callbacks: HandshakeCallbacks<TAccount>, ...args: unknown[]): Promise<AuthResult<TAccount>>;
|
|
107
|
+
/**
|
|
108
|
+
* Handle redirect with PKCE challenge.
|
|
109
|
+
*/
|
|
110
|
+
private handleRedirectWithPKCE;
|
|
111
|
+
/**
|
|
112
|
+
* Handle callback with PKCE verification.
|
|
113
|
+
*/
|
|
114
|
+
private handleCallbackWithPKCE;
|
|
115
|
+
/**
|
|
116
|
+
* Exchange code for token using PKCE.
|
|
117
|
+
*/
|
|
118
|
+
private exchangeCodeWithPKCE;
|
|
119
|
+
/**
|
|
120
|
+
* Fetch user profile with additional fields.
|
|
121
|
+
*/
|
|
122
|
+
protected fetchUserProfile(accessToken: string): Promise<OAuthProfile | {
|
|
123
|
+
error: string;
|
|
124
|
+
}>;
|
|
125
|
+
/**
|
|
126
|
+
* Map Twitter/X profile to standard OAuthProfile.
|
|
127
|
+
*/
|
|
128
|
+
protected mapProfile(data: unknown, accessToken: string): OAuthProfile;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=twitter-x.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitter-x.d.ts","sourceRoot":"","sources":["../../src/strategies/twitter-x.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,aAAa,EAA2C,MAAM,iBAAiB,CAAC;AAEzF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE;YACf,eAAe,EAAE,MAAM,CAAC;YACxB,eAAe,EAAE,MAAM,CAAC;YACxB,WAAW,EAAE,MAAM,CAAC;YACpB,YAAY,EAAE,MAAM,CAAC;SACtB,CAAC;KACH,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,qBAAa,gBAAgB,CAAC,QAAQ,CAAE,SAAQ,aAAa,CAAC,QAAQ,CAAC;IACrE,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAA2B;gBAEtD,OAAO,EAAE,uBAAuB;IAc5C;;OAEG;IACG,YAAY,CAChB,SAAS,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EACvC,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAehC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;OAEG;YACW,sBAAsB;IAqFpC;;OAEG;YACW,oBAAoB;IA0ClC;;OAEG;cACa,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IA6B5C;;OAEG;IAEH,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,YAAY;CAYvE"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { randomBytes, createHash } from 'crypto';
|
|
2
|
+
import { OAuthStrategy } from './oauth-base.js';
|
|
3
|
+
/**
|
|
4
|
+
* Twitter/X OAuth 2.0 authentication strategy.
|
|
5
|
+
*
|
|
6
|
+
* Uses OAuth 2.0 with PKCE (Proof Key for Code Exchange) as required by
|
|
7
|
+
* Twitter's API v2.
|
|
8
|
+
*
|
|
9
|
+
* Note: Twitter does not provide email addresses through the OAuth API.
|
|
10
|
+
* The profile will have email set to null.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const hs = new Handshake({
|
|
15
|
+
* findAccount: async (email) => db.accounts.findByEmail(email),
|
|
16
|
+
* findOrCreateFromOAuth: async (provider, profile) => {
|
|
17
|
+
* // Note: Twitter doesn't provide email, use profile.id for lookup
|
|
18
|
+
* let account = await db.accounts.findByProviderId(provider, profile.id);
|
|
19
|
+
* if (!account) {
|
|
20
|
+
* account = await db.accounts.create({
|
|
21
|
+
* name: profile.name,
|
|
22
|
+
* providerId: profile.id,
|
|
23
|
+
* provider,
|
|
24
|
+
* // You may want to prompt user for email separately
|
|
25
|
+
* });
|
|
26
|
+
* }
|
|
27
|
+
* return account;
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* hs.use(new TwitterXStrategy({
|
|
32
|
+
* clientId: process.env.TWITTER_CLIENT_ID!,
|
|
33
|
+
* clientSecret: process.env.TWITTER_CLIENT_SECRET!,
|
|
34
|
+
* redirectUri: 'http://localhost:3000/auth/twitter/callback',
|
|
35
|
+
* }));
|
|
36
|
+
*
|
|
37
|
+
* // Routes
|
|
38
|
+
* app.get('/auth/twitter', async (req, res) => {
|
|
39
|
+
* const result = await hs.authenticate('twitter-x', req, res, 'redirect');
|
|
40
|
+
* if ('redirectUrl' in result) {
|
|
41
|
+
* res.redirect(result.redirectUrl);
|
|
42
|
+
* }
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* app.get('/auth/twitter/callback', async (req, res) => {
|
|
46
|
+
* const result = await hs.authenticate('twitter-x', req, res, 'callback');
|
|
47
|
+
* if (result.account) {
|
|
48
|
+
* login(req, result.account);
|
|
49
|
+
* res.redirect('/dashboard');
|
|
50
|
+
* } else {
|
|
51
|
+
* res.status(401).send(result.error);
|
|
52
|
+
* }
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export class TwitterXStrategy extends OAuthStrategy {
|
|
57
|
+
codeVerifierCookieName = 'twitter_code_verifier';
|
|
58
|
+
constructor(options) {
|
|
59
|
+
super({
|
|
60
|
+
name: 'twitter-x',
|
|
61
|
+
clientId: options.clientId,
|
|
62
|
+
clientSecret: options.clientSecret,
|
|
63
|
+
redirectUri: options.redirectUri,
|
|
64
|
+
authorizeUrl: 'https://twitter.com/i/oauth2/authorize',
|
|
65
|
+
tokenUrl: 'https://api.twitter.com/2/oauth2/token',
|
|
66
|
+
userInfoUrl: 'https://api.twitter.com/2/users/me',
|
|
67
|
+
scopes: options.scopes ?? ['tweet.read', 'users.read'],
|
|
68
|
+
stateCookieName: 'twitter_oauth_state',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Override authenticate to handle PKCE flow.
|
|
73
|
+
*/
|
|
74
|
+
async authenticate(callbacks, ...args) {
|
|
75
|
+
const [req, res, phase] = args;
|
|
76
|
+
if (phase === 'redirect') {
|
|
77
|
+
return this.handleRedirectWithPKCE(req, res);
|
|
78
|
+
}
|
|
79
|
+
else if (phase === 'callback') {
|
|
80
|
+
return this.handleCallbackWithPKCE(callbacks, req, res);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
return {
|
|
84
|
+
account: null,
|
|
85
|
+
error: `Invalid OAuth phase: ${phase}. Use 'redirect' or 'callback'`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Handle redirect with PKCE challenge.
|
|
91
|
+
*/
|
|
92
|
+
handleRedirectWithPKCE(_req, res) {
|
|
93
|
+
// Generate PKCE code verifier and challenge
|
|
94
|
+
const codeVerifier = randomBytes(32).toString('base64url');
|
|
95
|
+
const codeChallenge = createHash('sha256')
|
|
96
|
+
.update(codeVerifier)
|
|
97
|
+
.digest('base64url');
|
|
98
|
+
// Generate state for CSRF protection
|
|
99
|
+
const state = randomBytes(32).toString('hex');
|
|
100
|
+
// Store state and code verifier in cookies
|
|
101
|
+
const cookieOptions = {
|
|
102
|
+
httpOnly: true,
|
|
103
|
+
secure: process.env.NODE_ENV === 'production',
|
|
104
|
+
sameSite: 'lax',
|
|
105
|
+
maxAge: 10 * 60 * 1000, // 10 minutes
|
|
106
|
+
};
|
|
107
|
+
res.cookie(this.stateCookieName, state, cookieOptions);
|
|
108
|
+
res.cookie(this.codeVerifierCookieName, codeVerifier, cookieOptions);
|
|
109
|
+
// Build authorization URL with PKCE
|
|
110
|
+
const params = new URLSearchParams({
|
|
111
|
+
client_id: this.clientId,
|
|
112
|
+
redirect_uri: this.redirectUri,
|
|
113
|
+
response_type: 'code',
|
|
114
|
+
scope: this.scopes.join(' '),
|
|
115
|
+
state,
|
|
116
|
+
code_challenge: codeChallenge,
|
|
117
|
+
code_challenge_method: 'S256',
|
|
118
|
+
});
|
|
119
|
+
const redirectUrl = `${this.authorizeUrl}?${params.toString()}`;
|
|
120
|
+
return {
|
|
121
|
+
account: null,
|
|
122
|
+
redirectUrl,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Handle callback with PKCE verification.
|
|
127
|
+
*/
|
|
128
|
+
async handleCallbackWithPKCE(callbacks, req, res) {
|
|
129
|
+
// Get state, code, and verifier
|
|
130
|
+
const { state: returnedState, code, error, error_description } = req.query;
|
|
131
|
+
// Check for OAuth error response
|
|
132
|
+
if (error) {
|
|
133
|
+
return {
|
|
134
|
+
account: null,
|
|
135
|
+
error: error_description || error,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Verify state
|
|
139
|
+
const storedState = req.cookies?.[this.stateCookieName];
|
|
140
|
+
const codeVerifier = req.cookies?.[this.codeVerifierCookieName];
|
|
141
|
+
// Clear cookies
|
|
142
|
+
res.clearCookie(this.stateCookieName);
|
|
143
|
+
res.clearCookie(this.codeVerifierCookieName);
|
|
144
|
+
if (!storedState || storedState !== returnedState) {
|
|
145
|
+
return {
|
|
146
|
+
account: null,
|
|
147
|
+
error: 'Invalid OAuth state. Possible CSRF attack or expired session.',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (!code) {
|
|
151
|
+
return {
|
|
152
|
+
account: null,
|
|
153
|
+
error: 'No authorization code received',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (!codeVerifier) {
|
|
157
|
+
return {
|
|
158
|
+
account: null,
|
|
159
|
+
error: 'Missing PKCE code verifier. Session may have expired.',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Exchange code for token with PKCE
|
|
163
|
+
const tokenResult = await this.exchangeCodeWithPKCE(code, codeVerifier);
|
|
164
|
+
if ('error' in tokenResult) {
|
|
165
|
+
return {
|
|
166
|
+
account: null,
|
|
167
|
+
error: tokenResult.error,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// Fetch user profile
|
|
171
|
+
const profileResult = await this.fetchUserProfile(tokenResult.access_token);
|
|
172
|
+
if ('error' in profileResult) {
|
|
173
|
+
return {
|
|
174
|
+
account: null,
|
|
175
|
+
error: profileResult.error,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// Check for required callback
|
|
179
|
+
if (!callbacks.findOrCreateFromOAuth) {
|
|
180
|
+
return {
|
|
181
|
+
account: null,
|
|
182
|
+
error: 'findOrCreateFromOAuth callback is required for OAuth strategies',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// Let the application handle account creation/lookup
|
|
186
|
+
const account = await callbacks.findOrCreateFromOAuth(this.name, profileResult);
|
|
187
|
+
return {
|
|
188
|
+
account,
|
|
189
|
+
strategy: this.name,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Exchange code for token using PKCE.
|
|
194
|
+
*/
|
|
195
|
+
async exchangeCodeWithPKCE(code, codeVerifier) {
|
|
196
|
+
try {
|
|
197
|
+
// Twitter requires Basic auth for token exchange
|
|
198
|
+
const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
|
|
199
|
+
const params = new URLSearchParams({
|
|
200
|
+
code,
|
|
201
|
+
grant_type: 'authorization_code',
|
|
202
|
+
redirect_uri: this.redirectUri,
|
|
203
|
+
code_verifier: codeVerifier,
|
|
204
|
+
});
|
|
205
|
+
const response = await fetch(this.tokenUrl, {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: {
|
|
208
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
209
|
+
Authorization: `Basic ${credentials}`,
|
|
210
|
+
Accept: 'application/json',
|
|
211
|
+
},
|
|
212
|
+
body: params.toString(),
|
|
213
|
+
});
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
const errorData = await response.json().catch(() => ({}));
|
|
216
|
+
return {
|
|
217
|
+
error: errorData.error_description ||
|
|
218
|
+
errorData.error ||
|
|
219
|
+
`Token exchange failed: ${response.status}`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return (await response.json());
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
return {
|
|
226
|
+
error: `Token exchange failed: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Fetch user profile with additional fields.
|
|
232
|
+
*/
|
|
233
|
+
async fetchUserProfile(accessToken) {
|
|
234
|
+
try {
|
|
235
|
+
// Request additional user fields
|
|
236
|
+
const params = new URLSearchParams({
|
|
237
|
+
'user.fields': 'id,name,username,profile_image_url,description',
|
|
238
|
+
});
|
|
239
|
+
const response = await fetch(`${this.userInfoUrl}?${params.toString()}`, {
|
|
240
|
+
headers: {
|
|
241
|
+
Authorization: `Bearer ${accessToken}`,
|
|
242
|
+
Accept: 'application/json',
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
if (!response.ok) {
|
|
246
|
+
return {
|
|
247
|
+
error: `Failed to fetch user profile: ${response.status}`,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const data = await response.json();
|
|
251
|
+
return this.mapProfile(data, accessToken);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
return {
|
|
255
|
+
error: `Failed to fetch user profile: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Map Twitter/X profile to standard OAuthProfile.
|
|
261
|
+
*/
|
|
262
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
263
|
+
mapProfile(data, accessToken) {
|
|
264
|
+
const response = data;
|
|
265
|
+
const profile = response.data;
|
|
266
|
+
return {
|
|
267
|
+
id: profile.id,
|
|
268
|
+
email: null, // Twitter doesn't provide email through OAuth
|
|
269
|
+
name: profile.name,
|
|
270
|
+
picture: profile.profile_image_url ?? null,
|
|
271
|
+
raw: profile,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=twitter-x.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitter-x.js","sourceRoot":"","sources":["../../src/strategies/twitter-x.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AuthResult, Strategy, HandshakeCallbacks } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Username/Password authentication strategy.
|
|
4
|
+
*
|
|
5
|
+
* Authenticates users with an identifier (e.g., email, username) and password.
|
|
6
|
+
* Uses the findAccount and verifyPassword callbacks from Handshake.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const hs = new Handshake({
|
|
11
|
+
* findAccount: async (email) => db.accounts.findByEmail(email),
|
|
12
|
+
* verifyPassword: async (account, password) => {
|
|
13
|
+
* return bcrypt.compare(password, account.passwordHash);
|
|
14
|
+
* },
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* hs.use(new UsernamePasswordStrategy());
|
|
18
|
+
*
|
|
19
|
+
* // In your login route
|
|
20
|
+
* const result = await hs.authenticate('username-password', email, password);
|
|
21
|
+
* if (result.account) {
|
|
22
|
+
* login(req, result.account);
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare class UsernamePasswordStrategy<TAccount> implements Strategy<TAccount> {
|
|
27
|
+
readonly name = "username-password";
|
|
28
|
+
/**
|
|
29
|
+
* Authenticate with identifier and password.
|
|
30
|
+
*
|
|
31
|
+
* @param callbacks - Handshake callbacks (findAccount, verifyPassword)
|
|
32
|
+
* @param identifier - The account identifier (e.g., email, username)
|
|
33
|
+
* @param password - The password to verify
|
|
34
|
+
* @returns Authentication result with account or error
|
|
35
|
+
*/
|
|
36
|
+
authenticate(callbacks: HandshakeCallbacks<TAccount>, ...args: unknown[]): Promise<AuthResult<TAccount>>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=username-password.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"username-password.d.ts","sourceRoot":"","sources":["../../src/strategies/username-password.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,wBAAwB,CAAC,QAAQ,CAAE,YAAW,QAAQ,CAAC,QAAQ,CAAC;IAC3E,QAAQ,CAAC,IAAI,uBAAuB;IAEpC;;;;;;;OAOG;IACG,YAAY,CAChB,SAAS,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EACvC,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;CAgCjC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Username/Password authentication strategy.
|
|
3
|
+
*
|
|
4
|
+
* Authenticates users with an identifier (e.g., email, username) and password.
|
|
5
|
+
* Uses the findAccount and verifyPassword callbacks from Handshake.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const hs = new Handshake({
|
|
10
|
+
* findAccount: async (email) => db.accounts.findByEmail(email),
|
|
11
|
+
* verifyPassword: async (account, password) => {
|
|
12
|
+
* return bcrypt.compare(password, account.passwordHash);
|
|
13
|
+
* },
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* hs.use(new UsernamePasswordStrategy());
|
|
17
|
+
*
|
|
18
|
+
* // In your login route
|
|
19
|
+
* const result = await hs.authenticate('username-password', email, password);
|
|
20
|
+
* if (result.account) {
|
|
21
|
+
* login(req, result.account);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class UsernamePasswordStrategy {
|
|
26
|
+
name = 'username-password';
|
|
27
|
+
/**
|
|
28
|
+
* Authenticate with identifier and password.
|
|
29
|
+
*
|
|
30
|
+
* @param callbacks - Handshake callbacks (findAccount, verifyPassword)
|
|
31
|
+
* @param identifier - The account identifier (e.g., email, username)
|
|
32
|
+
* @param password - The password to verify
|
|
33
|
+
* @returns Authentication result with account or error
|
|
34
|
+
*/
|
|
35
|
+
async authenticate(callbacks, ...args) {
|
|
36
|
+
const [identifier, password] = args;
|
|
37
|
+
if (!identifier || !password) {
|
|
38
|
+
return { account: null, error: 'Identifier and password are required' };
|
|
39
|
+
}
|
|
40
|
+
if (!callbacks.verifyPassword) {
|
|
41
|
+
return { account: null, error: 'verifyPassword callback is required for username-password strategy' };
|
|
42
|
+
}
|
|
43
|
+
// Find the account
|
|
44
|
+
const account = await callbacks.findAccount(identifier);
|
|
45
|
+
// Always call verifyPassword even if account is null to prevent timing attacks.
|
|
46
|
+
// The verifyPassword callback should handle null accounts appropriately
|
|
47
|
+
// (e.g., by doing a dummy comparison that takes the same time).
|
|
48
|
+
// However, since the callback signature requires an account, we'll check first
|
|
49
|
+
// and rely on the overall operation timing being similar.
|
|
50
|
+
if (!account) {
|
|
51
|
+
return { account: null, error: 'Invalid credentials' };
|
|
52
|
+
}
|
|
53
|
+
// Verify the password
|
|
54
|
+
const isValid = await callbacks.verifyPassword(account, password);
|
|
55
|
+
if (!isValid) {
|
|
56
|
+
return { account: null, error: 'Invalid credentials' };
|
|
57
|
+
}
|
|
58
|
+
return { account, strategy: this.name };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=username-password.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"username-password.js","sourceRoot":"","sources":["../../src/strategies/username-password.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,OAAO,wBAAwB;IAC1B,IAAI,GAAG,mBAAmB,CAAC;IAEpC;;;;;;;OAOG;IACH,KAAK,CAAC,YAAY,CAChB,SAAuC,EACvC,GAAG,IAAe;QAElB,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,IAAwB,CAAC;QAExD,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC7B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC;QAC1E,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC;YAC9B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,oEAAoE,EAAE,CAAC;QACxG,CAAC;QAED,mBAAmB;QACnB,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QAExD,gFAAgF;QAChF,wEAAwE;QACxE,gEAAgE;QAChE,+EAA+E;QAC/E,0DAA0D;QAC1D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;QACzD,CAAC;QAED,sBAAsB;QACtB,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAElE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;QACzD,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handshake-auth",
|
|
3
3
|
"description": "Lightweight, storage-agnostic authentication for Express.js",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
|
-
"url": "
|
|
42
|
+
"url": "https://codeberg.org/chilts/handshake-auth.git"
|
|
43
43
|
},
|
|
44
44
|
"author": {
|
|
45
45
|
"name": "Andrew Chilton",
|