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 +15 -0
- package/ReadMe.md +136 -0
- package/dist/env.d.ts +26 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +43 -0
- package/dist/env.js.map +1 -0
- package/dist/gatekeeper-auth.d.ts +43 -0
- package/dist/gatekeeper-auth.d.ts.map +1 -0
- package/dist/gatekeeper-auth.js +62 -0
- package/dist/gatekeeper-auth.js.map +1 -0
- package/dist/handshake.d.ts +43 -0
- package/dist/handshake.d.ts.map +1 -0
- package/dist/handshake.js +62 -0
- package/dist/handshake.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/express.d.ts +77 -0
- package/dist/middleware/express.d.ts.map +1 -0
- package/dist/middleware/express.js +77 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/strategies/index.d.ts +2 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +2 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/password.d.ts +20 -0
- package/dist/strategies/password.d.ts.map +1 -0
- package/dist/strategies/password.js +43 -0
- package/dist/strategies/password.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -0
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
|
package/dist/env.js.map
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|