handshake-auth 0.1.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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Andrew Chilton
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/ReadMe.md ADDED
@@ -0,0 +1,136 @@
1
+ # Handshake Auth
2
+
3
+ Lightweight, storage-agnostic authentication for Express.js.
4
+
5
+ > **Work in Progress** - This library is under active development and not yet ready for production use.
6
+
7
+ ## Overview
8
+
9
+ Handshake Auth is a lightweight authentication library that follows the strategy pattern similar to Passport.js, but without requiring a database connection. You provide callbacks for all storage operations (Inversion of Control), giving you complete control over how accounts are stored and retrieved.
10
+
11
+ ### Core Philosophy
12
+
13
+ > "Handshake Auth authenticates requests. It does not log users in."
14
+
15
+ The library handles the "handshakes" and "proofs" without demanding a seat at your database table. After successful authentication, you receive an Account object and decide what to do with it.
16
+
17
+ ## Features
18
+
19
+ - **Storage-agnostic** - You provide callbacks, you own your data
20
+ - **Express-only** - Focused on Express.js with `cookie-session`
21
+ - **TypeScript-first** - Full type safety with generics
22
+ - **Modern** - async/await throughout, ESM-first
23
+ - **Strategies included** - Password, Magic Link, Google OAuth, GitHub OAuth
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install handshake-auth
29
+ ```
30
+
31
+ ## Basic Usage
32
+
33
+ ```typescript
34
+ import { Handshake, PasswordStrategy } from 'handshake-auth';
35
+
36
+ // Define your account type
37
+ interface Account {
38
+ id: string;
39
+ email: string;
40
+ passwordHash: string;
41
+ }
42
+
43
+ // Create a Handshake instance with your callbacks
44
+ const hs = new Handshake<Account>({
45
+ findAccount: async (email) => {
46
+ // Your database lookup here
47
+ return db.accounts.findByEmail(email);
48
+ },
49
+ verifyPassword: async (account, password) => {
50
+ // Your password verification here (e.g., bcrypt.compare)
51
+ return await bcrypt.compare(password, account.passwordHash);
52
+ },
53
+ });
54
+
55
+ // Register the password strategy
56
+ hs.use(new PasswordStrategy());
57
+
58
+ // Authenticate
59
+ const result = await hs.authenticate('password', 'user@example.com', 'their-password');
60
+
61
+ if (result.account) {
62
+ // Authentication successful
63
+ console.log('Logged in:', result.account.email);
64
+ } else {
65
+ // Authentication failed
66
+ console.log('Error:', result.error);
67
+ }
68
+ ```
69
+
70
+ ## Express Integration
71
+
72
+ Use with Express and `cookie-session` for a complete authentication setup:
73
+
74
+ ```typescript
75
+ import express from 'express';
76
+ import cookieSession from 'cookie-session';
77
+ import {
78
+ Handshake,
79
+ PasswordStrategy,
80
+ handshakeMiddleware,
81
+ login,
82
+ logout,
83
+ isLoggedIn,
84
+ } from 'handshake-auth';
85
+
86
+ const app = express();
87
+ app.use(express.json());
88
+
89
+ // Configure cookie-session
90
+ app.use(cookieSession({
91
+ name: 'session',
92
+ keys: [process.env.SESSION_SECRET!],
93
+ maxAge: 24 * 60 * 60 * 1000, // 24 hours
94
+ }));
95
+
96
+ // Set up Handshake
97
+ const hs = new Handshake<Account>({
98
+ findAccount: async (email) => db.accounts.findByEmail(email),
99
+ verifyPassword: async (account, password) => bcrypt.compare(password, account.passwordHash),
100
+ });
101
+ hs.use(new PasswordStrategy());
102
+
103
+ // Add middleware (attaches authenticate helper to req)
104
+ app.use(handshakeMiddleware(hs));
105
+
106
+ // Login route
107
+ app.post('/login', async (req, res) => {
108
+ const { email, password } = req.body;
109
+ const result = await hs.authenticate('password', email, password);
110
+
111
+ if (result.account) {
112
+ login(req, result.account);
113
+ res.json({ success: true });
114
+ } else {
115
+ res.status(401).json({ error: result.error });
116
+ }
117
+ });
118
+
119
+ // Logout route
120
+ app.post('/logout', (req, res) => {
121
+ logout(req);
122
+ res.json({ success: true });
123
+ });
124
+
125
+ // Protected route
126
+ app.get('/me', (req, res) => {
127
+ if (!isLoggedIn(req)) {
128
+ return res.status(401).json({ error: 'Not logged in' });
129
+ }
130
+ res.json({ accountId: req.session?.accountId });
131
+ });
132
+ ```
133
+
134
+ ## License
135
+
136
+ ISC
package/dist/env.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Get an environment variable or return undefined if not set
3
+ */
4
+ export declare function getEnv(key: string): string | undefined;
5
+ /**
6
+ * Get an environment variable or throw if not set
7
+ */
8
+ export declare function requireEnv(key: string): string;
9
+ /**
10
+ * Configuration for checking if a strategy has its required environment variables
11
+ */
12
+ export interface StrategyEnvConfig {
13
+ required: string[];
14
+ optional?: string[];
15
+ }
16
+ /**
17
+ * Check if a strategy is configured by verifying its required environment variables.
18
+ * Returns true if all required vars are set, false otherwise.
19
+ */
20
+ export declare function checkStrategyEnv(config: StrategyEnvConfig): boolean;
21
+ /**
22
+ * Get all environment values for a strategy.
23
+ * Returns an object with values for required and optional vars, or null if not all required vars are set.
24
+ */
25
+ export declare function getStrategyEnv<T extends Record<string, string | undefined>>(config: StrategyEnvConfig): T | null;
26
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEtD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAEnE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EACzE,MAAM,EAAE,iBAAiB,GACxB,CAAC,GAAG,IAAI,CAkBV"}
package/dist/env.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Get an environment variable or return undefined if not set
3
+ */
4
+ export function getEnv(key) {
5
+ return process.env[key];
6
+ }
7
+ /**
8
+ * Get an environment variable or throw if not set
9
+ */
10
+ export function requireEnv(key) {
11
+ const value = process.env[key];
12
+ if (value === undefined) {
13
+ throw new Error(`Missing required environment variable: ${key}`);
14
+ }
15
+ return value;
16
+ }
17
+ /**
18
+ * Check if a strategy is configured by verifying its required environment variables.
19
+ * Returns true if all required vars are set, false otherwise.
20
+ */
21
+ export function checkStrategyEnv(config) {
22
+ return config.required.every((key) => process.env[key] !== undefined);
23
+ }
24
+ /**
25
+ * Get all environment values for a strategy.
26
+ * Returns an object with values for required and optional vars, or null if not all required vars are set.
27
+ */
28
+ export function getStrategyEnv(config) {
29
+ if (!checkStrategyEnv(config)) {
30
+ return null;
31
+ }
32
+ const result = {};
33
+ for (const key of config.required) {
34
+ result[key] = process.env[key];
35
+ }
36
+ if (config.optional) {
37
+ for (const key of config.optional) {
38
+ result[key] = process.env[key];
39
+ }
40
+ }
41
+ return result;
42
+ }
43
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,GAAW;IAChC,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,0CAA0C,GAAG,EAAE,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAUD;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAyB;IACxD,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC;AACxE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAyB;IAEzB,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAuC,EAAE,CAAC;IAEtD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YAClC,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,OAAO,MAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { AuthResult, Strategy, GatekeeperOptions } from './types.js';
2
+ /**
3
+ * Main Gatekeeper class for managing authentication strategies.
4
+ *
5
+ * @typeParam TAccount - The shape of your account object
6
+ */
7
+ export declare class Gatekeeper<TAccount> {
8
+ private options;
9
+ private strategies;
10
+ constructor(options: GatekeeperOptions<TAccount>);
11
+ /**
12
+ * Register an authentication strategy.
13
+ *
14
+ * @param strategy - The strategy to register
15
+ * @returns this (for chaining)
16
+ */
17
+ use(strategy: Strategy<TAccount>): this;
18
+ /**
19
+ * Authenticate using a named strategy.
20
+ *
21
+ * @param strategyName - The name of the strategy to use
22
+ * @param args - Arguments to pass to the strategy's authenticate method
23
+ * @returns The authentication result
24
+ */
25
+ authenticate(strategyName: string, ...args: unknown[]): Promise<AuthResult<TAccount>>;
26
+ /**
27
+ * Get a registered strategy by name.
28
+ *
29
+ * @param name - The strategy name
30
+ * @returns The strategy, or undefined if not registered
31
+ */
32
+ getStrategy(name: string): Strategy<TAccount> | undefined;
33
+ /**
34
+ * Get the list of registered strategy names.
35
+ */
36
+ get registeredStrategies(): string[];
37
+ /**
38
+ * Get the callbacks/options provided to this Gatekeeper instance.
39
+ * Strategies can use this to access shared callbacks like findAccount.
40
+ */
41
+ get callbacks(): GatekeeperOptions<TAccount>;
42
+ }
43
+ //# sourceMappingURL=gatekeeper-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gatekeeper-auth.d.ts","sourceRoot":"","sources":["../src/gatekeeper-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;GAIG;AACH,qBAAa,UAAU,CAAC,QAAQ;IAGlB,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,UAAU,CAAyC;gBAEvC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,CAAC;IAExD;;;;;OAKG;IACH,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,IAAI;IAQvC;;;;;;OAMG;IACG,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAQ3F;;;;;OAKG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,SAAS;IAIzD;;OAEG;IACH,IAAI,oBAAoB,IAAI,MAAM,EAAE,CAEnC;IAED;;;OAGG;IACH,IAAI,SAAS,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAE3C;CACF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Main Gatekeeper class for managing authentication strategies.
3
+ *
4
+ * @typeParam TAccount - The shape of your account object
5
+ */
6
+ export class Gatekeeper {
7
+ options;
8
+ strategies = new Map();
9
+ constructor(options) {
10
+ this.options = options;
11
+ }
12
+ /**
13
+ * Register an authentication strategy.
14
+ *
15
+ * @param strategy - The strategy to register
16
+ * @returns this (for chaining)
17
+ */
18
+ use(strategy) {
19
+ if (this.strategies.has(strategy.name)) {
20
+ throw new Error(`Strategy "${strategy.name}" is already registered`);
21
+ }
22
+ this.strategies.set(strategy.name, strategy);
23
+ return this;
24
+ }
25
+ /**
26
+ * Authenticate using a named strategy.
27
+ *
28
+ * @param strategyName - The name of the strategy to use
29
+ * @param args - Arguments to pass to the strategy's authenticate method
30
+ * @returns The authentication result
31
+ */
32
+ async authenticate(strategyName, ...args) {
33
+ const strategy = this.strategies.get(strategyName);
34
+ if (!strategy) {
35
+ return { account: null, error: `Unknown strategy: ${strategyName}` };
36
+ }
37
+ return strategy.authenticate(this.options, ...args);
38
+ }
39
+ /**
40
+ * Get a registered strategy by name.
41
+ *
42
+ * @param name - The strategy name
43
+ * @returns The strategy, or undefined if not registered
44
+ */
45
+ getStrategy(name) {
46
+ return this.strategies.get(name);
47
+ }
48
+ /**
49
+ * Get the list of registered strategy names.
50
+ */
51
+ get registeredStrategies() {
52
+ return Array.from(this.strategies.keys());
53
+ }
54
+ /**
55
+ * Get the callbacks/options provided to this Gatekeeper instance.
56
+ * Strategies can use this to access shared callbacks like findAccount.
57
+ */
58
+ get callbacks() {
59
+ return this.options;
60
+ }
61
+ }
62
+ //# sourceMappingURL=gatekeeper-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gatekeeper-auth.js","sourceRoot":"","sources":["../src/gatekeeper-auth.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,UAAU;IAGD;IAFZ,UAAU,GAAG,IAAI,GAAG,EAA8B,CAAC;IAE3D,YAAoB,OAAoC;QAApC,YAAO,GAAP,OAAO,CAA6B;IAAG,CAAC;IAE5D;;;;;OAKG;IACH,GAAG,CAAC,QAA4B;QAC9B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,aAAa,QAAQ,CAAC,IAAI,yBAAyB,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,YAAoB,EAAE,GAAG,IAAe;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACnD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,qBAAqB,YAAY,EAAE,EAAE,CAAC;QACvE,CAAC;QACD,OAAO,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACtD,CAAC;IAED;;;;;OAKG;IACH,WAAW,CAAC,IAAY;QACtB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,IAAI,oBAAoB;QACtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED;;;OAGG;IACH,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ import type { AuthResult, Strategy, HandshakeOptions } from './types.js';
2
+ /**
3
+ * Main Handshake class for managing authentication strategies.
4
+ *
5
+ * @typeParam TAccount - The shape of your account object
6
+ */
7
+ export declare class Handshake<TAccount> {
8
+ private options;
9
+ private strategies;
10
+ constructor(options: HandshakeOptions<TAccount>);
11
+ /**
12
+ * Register an authentication strategy.
13
+ *
14
+ * @param strategy - The strategy to register
15
+ * @returns this (for chaining)
16
+ */
17
+ use(strategy: Strategy<TAccount>): this;
18
+ /**
19
+ * Authenticate using a named strategy.
20
+ *
21
+ * @param strategyName - The name of the strategy to use
22
+ * @param args - Arguments to pass to the strategy's authenticate method
23
+ * @returns The authentication result
24
+ */
25
+ authenticate(strategyName: string, ...args: unknown[]): Promise<AuthResult<TAccount>>;
26
+ /**
27
+ * Get a registered strategy by name.
28
+ *
29
+ * @param name - The strategy name
30
+ * @returns The strategy, or undefined if not registered
31
+ */
32
+ getStrategy(name: string): Strategy<TAccount> | undefined;
33
+ /**
34
+ * Get the list of registered strategy names.
35
+ */
36
+ get registeredStrategies(): string[];
37
+ /**
38
+ * Get the callbacks/options provided to this Handshake instance.
39
+ * Strategies can use this to access shared callbacks like findAccount.
40
+ */
41
+ get callbacks(): HandshakeOptions<TAccount>;
42
+ }
43
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../src/handshake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEzE;;;;GAIG;AACH,qBAAa,SAAS,CAAC,QAAQ;IAGjB,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,UAAU,CAAyC;gBAEvC,OAAO,EAAE,gBAAgB,CAAC,QAAQ,CAAC;IAEvD;;;;;OAKG;IACH,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,IAAI;IAQvC;;;;;;OAMG;IACG,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAQ3F;;;;;OAKG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,SAAS;IAIzD;;OAEG;IACH,IAAI,oBAAoB,IAAI,MAAM,EAAE,CAEnC;IAED;;;OAGG;IACH,IAAI,SAAS,IAAI,gBAAgB,CAAC,QAAQ,CAAC,CAE1C;CACF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Main Handshake class for managing authentication strategies.
3
+ *
4
+ * @typeParam TAccount - The shape of your account object
5
+ */
6
+ export class Handshake {
7
+ options;
8
+ strategies = new Map();
9
+ constructor(options) {
10
+ this.options = options;
11
+ }
12
+ /**
13
+ * Register an authentication strategy.
14
+ *
15
+ * @param strategy - The strategy to register
16
+ * @returns this (for chaining)
17
+ */
18
+ use(strategy) {
19
+ if (this.strategies.has(strategy.name)) {
20
+ throw new Error(`Strategy "${strategy.name}" is already registered`);
21
+ }
22
+ this.strategies.set(strategy.name, strategy);
23
+ return this;
24
+ }
25
+ /**
26
+ * Authenticate using a named strategy.
27
+ *
28
+ * @param strategyName - The name of the strategy to use
29
+ * @param args - Arguments to pass to the strategy's authenticate method
30
+ * @returns The authentication result
31
+ */
32
+ async authenticate(strategyName, ...args) {
33
+ const strategy = this.strategies.get(strategyName);
34
+ if (!strategy) {
35
+ return { account: null, error: `Unknown strategy: ${strategyName}` };
36
+ }
37
+ return strategy.authenticate(this.options, ...args);
38
+ }
39
+ /**
40
+ * Get a registered strategy by name.
41
+ *
42
+ * @param name - The strategy name
43
+ * @returns The strategy, or undefined if not registered
44
+ */
45
+ getStrategy(name) {
46
+ return this.strategies.get(name);
47
+ }
48
+ /**
49
+ * Get the list of registered strategy names.
50
+ */
51
+ get registeredStrategies() {
52
+ return Array.from(this.strategies.keys());
53
+ }
54
+ /**
55
+ * Get the callbacks/options provided to this Handshake instance.
56
+ * Strategies can use this to access shared callbacks like findAccount.
57
+ */
58
+ get callbacks() {
59
+ return this.options;
60
+ }
61
+ }
62
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.js","sourceRoot":"","sources":["../src/handshake.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,SAAS;IAGA;IAFZ,UAAU,GAAG,IAAI,GAAG,EAA8B,CAAC;IAE3D,YAAoB,OAAmC;QAAnC,YAAO,GAAP,OAAO,CAA4B;IAAG,CAAC;IAE3D;;;;;OAKG;IACH,GAAG,CAAC,QAA4B;QAC9B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,aAAa,QAAQ,CAAC,IAAI,yBAAyB,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,YAAoB,EAAE,GAAG,IAAe;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACnD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,qBAAqB,YAAY,EAAE,EAAE,CAAC;QACvE,CAAC;QACD,OAAO,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACtD,CAAC;IAED;;;;;OAKG;IACH,WAAW,CAAC,IAAY;QACtB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,IAAI,oBAAoB;QACtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED;;;OAGG;IACH,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ export type { AuthResult, Strategy, HandshakeCallbacks, HandshakeOptions, OAuthProfile, } from './types.js';
2
+ export { Handshake } from './handshake.js';
3
+ export { PasswordStrategy } from './strategies/index.js';
4
+ export { handshakeMiddleware, login, logout, isLoggedIn, getSessionAccountId, } from './middleware/index.js';
5
+ export type { HandshakeSession, RequestWithAuth } from './middleware/index.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,UAAU,EACV,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,EACL,mBAAmB,EACnB,KAAK,EACL,MAAM,EACN,UAAU,EACV,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // Main class
2
+ export { Handshake } from './handshake.js';
3
+ // Strategies
4
+ export { PasswordStrategy } from './strategies/index.js';
5
+ // Express middleware
6
+ export { handshakeMiddleware, login, logout, isLoggedIn, getSessionAccountId, } from './middleware/index.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,aAAa;AACb,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,aAAa;AACb,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,qBAAqB;AACrB,OAAO,EACL,mBAAmB,EACnB,KAAK,EACL,MAAM,EACN,UAAU,EACV,mBAAmB,GACpB,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,77 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ import type { Handshake } from '../handshake.js';
3
+ import type { AuthResult } from '../types.js';
4
+ /**
5
+ * Session data stored in cookie-session.
6
+ * Users can extend this with their own fields.
7
+ */
8
+ export interface HandshakeSession {
9
+ accountId?: string;
10
+ [key: string]: unknown;
11
+ }
12
+ /**
13
+ * Express middleware that attaches Handshake's authenticate helper to the request.
14
+ *
15
+ * @param hs - The Handshake instance
16
+ * @returns Express middleware function
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const hs = new Handshake({ ... });
21
+ * hs.use(new PasswordStrategy());
22
+ * app.use(handshakeMiddleware(hs));
23
+ * ```
24
+ */
25
+ export declare function handshakeMiddleware<TAccount>(hs: Handshake<TAccount>): (req: Request, _res: Response, next: NextFunction) => void;
26
+ /**
27
+ * Store account information in the session after successful authentication.
28
+ *
29
+ * @param req - Express request with cookie-session
30
+ * @param account - The authenticated account
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const result = await hs.authenticate('password', email, password);
35
+ * if (result.account) {
36
+ * login(req, result.account);
37
+ * }
38
+ * ```
39
+ */
40
+ export declare function login<TAccount extends {
41
+ id: string;
42
+ }>(req: Request, account: TAccount): void;
43
+ /**
44
+ * Clear the session, logging the user out.
45
+ *
46
+ * @param req - Express request with cookie-session
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * app.post('/logout', (req, res) => {
51
+ * logout(req);
52
+ * res.json({ success: true });
53
+ * });
54
+ * ```
55
+ */
56
+ export declare function logout(req: Request): void;
57
+ /**
58
+ * Check if a user is currently logged in (has an accountId in session).
59
+ *
60
+ * @param req - Express request with cookie-session
61
+ * @returns true if logged in, false otherwise
62
+ */
63
+ export declare function isLoggedIn(req: Request): boolean;
64
+ /**
65
+ * Get the account ID from the session, if logged in.
66
+ *
67
+ * @param req - Express request with cookie-session
68
+ * @returns The account ID or undefined if not logged in
69
+ */
70
+ export declare function getSessionAccountId(req: Request): string | undefined;
71
+ /**
72
+ * Extended Express Request with Handshake's authenticate helper.
73
+ */
74
+ export interface RequestWithAuth<TAccount> extends Request {
75
+ authenticate: (strategyName: string, ...args: unknown[]) => Promise<AuthResult<TAccount>>;
76
+ }
77
+ //# sourceMappingURL=express.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/middleware/express.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,CAAC,QAAQ,CAAC,IAC3D,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAWhE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,KAAK,CAAC,QAAQ,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACnD,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,QAAQ,GAChB,IAAI,CAKN;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAEzC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEhD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEpE;AAED;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,QAAQ,CAAE,SAAQ,OAAO;IACxD,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;CAC3F"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Express middleware that attaches Handshake's authenticate helper to the request.
3
+ *
4
+ * @param hs - The Handshake instance
5
+ * @returns Express middleware function
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const hs = new Handshake({ ... });
10
+ * hs.use(new PasswordStrategy());
11
+ * app.use(handshakeMiddleware(hs));
12
+ * ```
13
+ */
14
+ export function handshakeMiddleware(hs) {
15
+ return (req, _res, next) => {
16
+ // Attach authenticate helper to request
17
+ req.authenticate = async (strategyName, ...args) => {
18
+ return hs.authenticate(strategyName, ...args);
19
+ };
20
+ next();
21
+ };
22
+ }
23
+ /**
24
+ * Store account information in the session after successful authentication.
25
+ *
26
+ * @param req - Express request with cookie-session
27
+ * @param account - The authenticated account
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const result = await hs.authenticate('password', email, password);
32
+ * if (result.account) {
33
+ * login(req, result.account);
34
+ * }
35
+ * ```
36
+ */
37
+ export function login(req, account) {
38
+ if (!req.session) {
39
+ throw new Error('Session not available. Make sure cookie-session middleware is configured.');
40
+ }
41
+ req.session.accountId = account.id;
42
+ }
43
+ /**
44
+ * Clear the session, logging the user out.
45
+ *
46
+ * @param req - Express request with cookie-session
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * app.post('/logout', (req, res) => {
51
+ * logout(req);
52
+ * res.json({ success: true });
53
+ * });
54
+ * ```
55
+ */
56
+ export function logout(req) {
57
+ req.session = null;
58
+ }
59
+ /**
60
+ * Check if a user is currently logged in (has an accountId in session).
61
+ *
62
+ * @param req - Express request with cookie-session
63
+ * @returns true if logged in, false otherwise
64
+ */
65
+ export function isLoggedIn(req) {
66
+ return req.session?.accountId !== undefined;
67
+ }
68
+ /**
69
+ * Get the account ID from the session, if logged in.
70
+ *
71
+ * @param req - Express request with cookie-session
72
+ * @returns The account ID or undefined if not logged in
73
+ */
74
+ export function getSessionAccountId(req) {
75
+ return req.session?.accountId;
76
+ }
77
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/middleware/express.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAW,EAAuB;IACnE,OAAO,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB,EAAQ,EAAE;QAChE,wCAAwC;QACvC,GAAiC,CAAC,YAAY,GAAG,KAAK,EACrD,YAAoB,EACpB,GAAG,IAAe,EACa,EAAE;YACjC,OAAO,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,CAAC;QAChD,CAAC,CAAC;QAEF,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,KAAK,CACnB,GAAY,EACZ,OAAiB;IAEjB,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;IAC/F,CAAC;IACD,GAAG,CAAC,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,MAAM,CAAC,GAAY;IACjC,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,OAAO,GAAG,CAAC,OAAO,EAAE,SAAS,KAAK,SAAS,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,OAAO,GAAG,CAAC,OAAO,EAAE,SAA+B,CAAC;AACtD,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { handshakeMiddleware, login, logout, isLoggedIn, getSessionAccountId, } from './express.js';
2
+ export type { HandshakeSession, RequestWithAuth } from './express.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,EACL,MAAM,EACN,UAAU,EACV,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { handshakeMiddleware, login, logout, isLoggedIn, getSessionAccountId, } from './express.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,EACL,MAAM,EACN,UAAU,EACV,mBAAmB,GACpB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { PasswordStrategy } from './password.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/strategies/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { PasswordStrategy } from './password.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/strategies/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { AuthResult, Strategy, HandshakeCallbacks } from '../types.js';
2
+ /**
3
+ * Password authentication strategy.
4
+ *
5
+ * Authenticates users with an identifier (e.g., email) and password.
6
+ * Uses the findAccount and verifyPassword callbacks from Handshake.
7
+ */
8
+ export declare class PasswordStrategy<TAccount> implements Strategy<TAccount> {
9
+ readonly name = "password";
10
+ /**
11
+ * Authenticate with identifier and password.
12
+ *
13
+ * @param callbacks - Handshake callbacks (findAccount, verifyPassword)
14
+ * @param identifier - The account identifier (e.g., email)
15
+ * @param password - The password to verify
16
+ * @returns Authentication result with account or error
17
+ */
18
+ authenticate(callbacks: HandshakeCallbacks<TAccount>, ...args: unknown[]): Promise<AuthResult<TAccount>>;
19
+ }
20
+ //# sourceMappingURL=password.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password.d.ts","sourceRoot":"","sources":["../../src/strategies/password.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE5E;;;;;GAKG;AACH,qBAAa,gBAAgB,CAAC,QAAQ,CAAE,YAAW,QAAQ,CAAC,QAAQ,CAAC;IACnE,QAAQ,CAAC,IAAI,cAAc;IAE3B;;;;;;;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,43 @@
1
+ /**
2
+ * Password authentication strategy.
3
+ *
4
+ * Authenticates users with an identifier (e.g., email) and password.
5
+ * Uses the findAccount and verifyPassword callbacks from Handshake.
6
+ */
7
+ export class PasswordStrategy {
8
+ name = 'password';
9
+ /**
10
+ * Authenticate with identifier and password.
11
+ *
12
+ * @param callbacks - Handshake callbacks (findAccount, verifyPassword)
13
+ * @param identifier - The account identifier (e.g., email)
14
+ * @param password - The password to verify
15
+ * @returns Authentication result with account or error
16
+ */
17
+ async authenticate(callbacks, ...args) {
18
+ const [identifier, password] = args;
19
+ if (!identifier || !password) {
20
+ return { account: null, error: 'Identifier and password are required' };
21
+ }
22
+ if (!callbacks.verifyPassword) {
23
+ return { account: null, error: 'verifyPassword callback is required for password strategy' };
24
+ }
25
+ // Find the account
26
+ const account = await callbacks.findAccount(identifier);
27
+ // Always call verifyPassword even if account is null to prevent timing attacks.
28
+ // The verifyPassword callback should handle null accounts appropriately
29
+ // (e.g., by doing a dummy comparison that takes the same time).
30
+ // However, since the callback signature requires an account, we'll check first
31
+ // and rely on the overall operation timing being similar.
32
+ if (!account) {
33
+ return { account: null, error: 'Invalid credentials' };
34
+ }
35
+ // Verify the password
36
+ const isValid = await callbacks.verifyPassword(account, password);
37
+ if (!isValid) {
38
+ return { account: null, error: 'Invalid credentials' };
39
+ }
40
+ return { account, strategy: this.name };
41
+ }
42
+ }
43
+ //# sourceMappingURL=password.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password.js","sourceRoot":"","sources":["../../src/strategies/password.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,OAAO,gBAAgB;IAClB,IAAI,GAAG,UAAU,CAAC;IAE3B;;;;;;;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,2DAA2D,EAAE,CAAC;QAC/F,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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * OAuth profile data returned from providers
3
+ */
4
+ export interface OAuthProfile {
5
+ id: string;
6
+ email: string | null;
7
+ name: string | null;
8
+ picture: string | null;
9
+ raw: Record<string, unknown>;
10
+ }
11
+ /**
12
+ * Result of an authentication attempt.
13
+ * Either contains an authenticated account or null with an optional error message.
14
+ */
15
+ export type AuthResult<TAccount> = {
16
+ account: TAccount;
17
+ strategy: string;
18
+ } | {
19
+ account: null;
20
+ error?: string;
21
+ };
22
+ /**
23
+ * Base interface for authentication strategies.
24
+ * Each strategy must have a unique name and an authenticate method.
25
+ * The authenticate method receives the Handshake's callbacks as the first argument.
26
+ */
27
+ export interface Strategy<TAccount> {
28
+ name: string;
29
+ authenticate(callbacks: HandshakeCallbacks<TAccount>, ...args: unknown[]): Promise<AuthResult<TAccount>>;
30
+ }
31
+ /**
32
+ * Callbacks provided by the application for account operations.
33
+ * These implement the Inversion of Control pattern - the library
34
+ * never touches the database directly.
35
+ */
36
+ export interface HandshakeCallbacks<TAccount> {
37
+ /**
38
+ * Find an account by identifier (email, username, etc.)
39
+ */
40
+ findAccount: (identifier: string) => Promise<TAccount | null>;
41
+ /**
42
+ * Verify a password against an account (you handle hashing)
43
+ */
44
+ verifyPassword?: (account: TAccount, password: string) => Promise<boolean>;
45
+ /**
46
+ * Store a magic link token for later verification
47
+ */
48
+ storeMagicToken?: (email: string, token: string, expiresAt: Date) => Promise<void>;
49
+ /**
50
+ * Verify a magic link token and return the associated email
51
+ */
52
+ verifyMagicToken?: (token: string) => Promise<{
53
+ email: string;
54
+ } | null>;
55
+ /**
56
+ * Find or create an account from OAuth profile data
57
+ */
58
+ findOrCreateFromOAuth?: (provider: string, profile: OAuthProfile) => Promise<TAccount>;
59
+ }
60
+ /**
61
+ * Options for configuring Handshake.
62
+ * Currently the same as HandshakeCallbacks, but may be extended in the future.
63
+ */
64
+ export type HandshakeOptions<TAccount> = HandshakeCallbacks<TAccount>;
65
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,CAAC,QAAQ,IAC3B;IACE,OAAO,EAAE,QAAQ,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN;;;;GAIG;AACH,MAAM,WAAW,QAAQ,CAAC,QAAQ;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CACV,SAAS,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EACvC,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB,CAAC,QAAQ;IAC1C;;OAEG;IACH,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAE9D;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3E;;OAEG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnF;;OAEG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAExE;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxF;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,CAAC,QAAQ,IAAI,kBAAkB,CAAC,QAAQ,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "handshake-auth",
3
+ "description": "Lightweight, storage-agnostic authentication for Express.js",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "tsc --watch",
22
+ "lint": "eslint src tests",
23
+ "test": "npm run lint && vitest run",
24
+ "test:watch": "vitest",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "dependencies": {
28
+ "cookie-session": "^2.1.1"
29
+ },
30
+ "devDependencies": {
31
+ "@eslint/js": "^9.39.2",
32
+ "@types/cookie-session": "^2.0.49",
33
+ "@types/express": "^5.0.6",
34
+ "@types/node": "^25.1.0",
35
+ "eslint": "^9.39.2",
36
+ "typescript": "^5.9.3",
37
+ "typescript-eslint": "^8.54.0",
38
+ "vitest": "^4.0.18"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/chilts/handshake-auth.git"
43
+ },
44
+ "author": {
45
+ "name": "Andrew Chilton",
46
+ "email": "andychilton@gmail.com",
47
+ "url": "https://chilts.org/"
48
+ },
49
+ "keywords": [
50
+ "authentication",
51
+ "auth",
52
+ "express",
53
+ "passport",
54
+ "oauth",
55
+ "google",
56
+ "github",
57
+ "magic-link",
58
+ "passwordless",
59
+ "session",
60
+ "cookie-session"
61
+ ],
62
+ "license": "ISC"
63
+ }