ideal-auth 0.3.0 → 0.4.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 +30 -5
- package/dist/hash/index.d.ts +10 -0
- package/dist/hash/index.js +32 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +6 -3
- package/skills/ideal-auth/SKILL.md +45 -5
package/README.md
CHANGED
|
@@ -171,7 +171,11 @@ const auth = createAuth({
|
|
|
171
171
|
|
|
172
172
|
### `createHash(config?)`
|
|
173
173
|
|
|
174
|
-
Returns a `HashInstance` using bcrypt.
|
|
174
|
+
Returns a `HashInstance` using bcrypt. Requires `bcryptjs` (optional peer dependency):
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
bun add bcryptjs
|
|
178
|
+
```
|
|
175
179
|
|
|
176
180
|
| Option | Type | Default |
|
|
177
181
|
| --- | --- | --- |
|
|
@@ -186,6 +190,27 @@ const hashed = await hash.make('password');
|
|
|
186
190
|
const valid = await hash.verify('password', hashed); // true
|
|
187
191
|
```
|
|
188
192
|
|
|
193
|
+
### Custom hash (bring your own)
|
|
194
|
+
|
|
195
|
+
Skip `bcryptjs` entirely by providing your own `HashInstance`:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { prehash } from 'ideal-auth';
|
|
199
|
+
import type { HashInstance } from 'ideal-auth';
|
|
200
|
+
|
|
201
|
+
// Bun native bcrypt (use prehash to prevent silent truncation at 72 bytes)
|
|
202
|
+
const hash: HashInstance = {
|
|
203
|
+
make: (password) => Bun.password.hash(prehash(password), { algorithm: 'bcrypt', cost: 12 }),
|
|
204
|
+
verify: (password, hash) => Bun.password.verify(prehash(password), hash),
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Bun argon2id (OWASP recommended — no prehash needed, no input length limit)
|
|
208
|
+
const hash: HashInstance = {
|
|
209
|
+
make: (password) => Bun.password.hash(password, { algorithm: 'argon2id' }),
|
|
210
|
+
verify: (password, hash) => Bun.password.verify(password, hash),
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
189
214
|
---
|
|
190
215
|
|
|
191
216
|
### Crypto Utilities
|
|
@@ -517,10 +542,10 @@ cookie: {
|
|
|
517
542
|
|
|
518
543
|
## Dependencies
|
|
519
544
|
|
|
520
|
-
| Package | Purpose |
|
|
521
|
-
| --- | --- |
|
|
522
|
-
| `iron-session` | Session sealing/unsealing (AES-256-CBC + HMAC) |
|
|
523
|
-
| `bcryptjs` | Password hashing |
|
|
545
|
+
| Package | Purpose | Required |
|
|
546
|
+
| --- | --- | --- |
|
|
547
|
+
| `iron-session` | Session sealing/unsealing (AES-256-CBC + HMAC) | Yes |
|
|
548
|
+
| `bcryptjs` | Password hashing (used by `createHash()`) | Optional — not needed if you provide your own `HashInstance` |
|
|
524
549
|
|
|
525
550
|
Zero framework imports. Works in Node, Bun, Deno, and edge runtimes.
|
|
526
551
|
|
package/dist/hash/index.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
import type { HashInstance, HashConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* SHA-256 prehash for bcrypt's 72-byte input limit.
|
|
4
|
+
* Passwords exceeding 72 UTF-8 bytes are hashed to a 44-char base64 string
|
|
5
|
+
* before being passed to bcrypt, preventing silent truncation.
|
|
6
|
+
*
|
|
7
|
+
* Only needed for bcrypt — argon2 has no input length limit.
|
|
8
|
+
* Applied automatically by `createHash()`. Use this when building
|
|
9
|
+
* a custom bcrypt `HashInstance` (e.g., with `Bun.password`).
|
|
10
|
+
*/
|
|
11
|
+
export declare function prehash(password: string): string;
|
|
2
12
|
export declare function createHash(config?: HashConfig): HashInstance;
|
package/dist/hash/index.js
CHANGED
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
import { createHash as nodeCryptoHash } from 'node:crypto';
|
|
2
|
-
import bcrypt from 'bcryptjs';
|
|
3
2
|
const DEFAULT_ROUNDS = 12;
|
|
4
3
|
const BCRYPT_MAX_BYTES = 72;
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* SHA-256 prehash for bcrypt's 72-byte input limit.
|
|
6
|
+
* Passwords exceeding 72 UTF-8 bytes are hashed to a 44-char base64 string
|
|
7
|
+
* before being passed to bcrypt, preventing silent truncation.
|
|
8
|
+
*
|
|
9
|
+
* Only needed for bcrypt — argon2 has no input length limit.
|
|
10
|
+
* Applied automatically by `createHash()`. Use this when building
|
|
11
|
+
* a custom bcrypt `HashInstance` (e.g., with `Bun.password`).
|
|
12
|
+
*/
|
|
13
|
+
export function prehash(password) {
|
|
14
|
+
if (Buffer.byteLength(password, 'utf8') <= BCRYPT_MAX_BYTES)
|
|
15
|
+
return password;
|
|
6
16
|
return nodeCryptoHash('sha256').update(password).digest('base64');
|
|
7
17
|
}
|
|
18
|
+
async function loadBcrypt() {
|
|
19
|
+
try {
|
|
20
|
+
return await import('bcryptjs');
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error('bcryptjs is required for createHash(). Install it as a dependency in your project.\n' +
|
|
24
|
+
'Alternatively, provide your own HashInstance (e.g., using Bun.password or argon2).');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
8
27
|
export function createHash(config) {
|
|
9
28
|
const rounds = config?.rounds ?? DEFAULT_ROUNDS;
|
|
29
|
+
let bcryptModule = null;
|
|
30
|
+
async function getBcrypt() {
|
|
31
|
+
if (!bcryptModule) {
|
|
32
|
+
bcryptModule = await loadBcrypt();
|
|
33
|
+
}
|
|
34
|
+
return bcryptModule;
|
|
35
|
+
}
|
|
10
36
|
return {
|
|
11
37
|
async make(password) {
|
|
12
38
|
if (!password)
|
|
13
39
|
throw new Error('password must not be empty');
|
|
14
|
-
const
|
|
40
|
+
const bcrypt = await getBcrypt();
|
|
15
41
|
const salt = await bcrypt.genSalt(rounds);
|
|
16
|
-
return bcrypt.hash(
|
|
42
|
+
return bcrypt.hash(prehash(password), salt);
|
|
17
43
|
},
|
|
18
44
|
async verify(password, hash) {
|
|
19
|
-
const
|
|
20
|
-
return bcrypt.compare(
|
|
45
|
+
const bcrypt = await getBcrypt();
|
|
46
|
+
return bcrypt.compare(prehash(password), hash);
|
|
21
47
|
},
|
|
22
48
|
};
|
|
23
49
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createAuth } from './auth';
|
|
2
|
-
export { createHash } from './hash';
|
|
2
|
+
export { createHash, prehash } from './hash';
|
|
3
3
|
export { generateToken } from './crypto/token';
|
|
4
4
|
export { signData, verifySignature } from './crypto/hmac';
|
|
5
5
|
export { encrypt, decrypt } from './crypto/encryption';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Auth
|
|
2
2
|
export { createAuth } from './auth';
|
|
3
3
|
// Hash
|
|
4
|
-
export { createHash } from './hash';
|
|
4
|
+
export { createHash, prehash } from './hash';
|
|
5
5
|
// Crypto utilities
|
|
6
6
|
export { generateToken } from './crypto/token';
|
|
7
7
|
export { signData, verifySignature } from './crypto/hmac';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ideal-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Auth primitives for the JS ecosystem. Zero framework dependencies.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsc",
|
|
@@ -25,17 +25,20 @@
|
|
|
25
25
|
"skills"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"iron-session": "^8.0.4"
|
|
29
|
-
"bcryptjs": "^3.0.3"
|
|
28
|
+
"iron-session": "^8.0.4"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
|
32
31
|
"@types/bcryptjs": "^3.0.0",
|
|
33
32
|
"@types/node": "^20"
|
|
34
33
|
},
|
|
35
34
|
"peerDependencies": {
|
|
35
|
+
"bcryptjs": "^3.0.3",
|
|
36
36
|
"typescript": "^5.0.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
|
+
"bcryptjs": {
|
|
40
|
+
"optional": true
|
|
41
|
+
},
|
|
39
42
|
"typescript": {
|
|
40
43
|
"optional": true
|
|
41
44
|
}
|
|
@@ -194,27 +194,67 @@ const auth = createAuth({
|
|
|
194
194
|
|
|
195
195
|
---
|
|
196
196
|
|
|
197
|
-
###
|
|
197
|
+
### Password Hashing
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
Password hashing is **only needed for the Laravel-style `attempt()` flow** (`hash` + `resolveUserByCredentials`). If you use `attemptUser`, `login(user)` directly, or `sessionFields` without credential verification, no `HashInstance` or `bcryptjs` is required.
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
type HashConfig = { rounds?: number }; // default: 12
|
|
201
|
+
ideal-auth accepts any `HashInstance`:
|
|
203
202
|
|
|
203
|
+
```typescript
|
|
204
204
|
type HashInstance = {
|
|
205
205
|
make(password: string): Promise<string>;
|
|
206
206
|
verify(password: string, hash: string): Promise<boolean>;
|
|
207
207
|
};
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
+
**`createHash()` (bcryptjs)** — built-in convenience. Requires `bcryptjs` as an optional peer dependency (`bun add bcryptjs`). Throws a clear error if not installed.
|
|
211
|
+
|
|
210
212
|
```typescript
|
|
211
213
|
import { createHash } from 'ideal-auth';
|
|
212
214
|
|
|
213
|
-
const hash = createHash({ rounds: 12 });
|
|
215
|
+
const hash = createHash({ rounds: 12 }); // default: 12
|
|
214
216
|
const hashed = await hash.make('password');
|
|
215
217
|
const valid = await hash.verify('password', hashed); // true
|
|
216
218
|
```
|
|
217
219
|
|
|
220
|
+
**Custom hash (bring your own)** — use your runtime's native hashing or a different algorithm. No `bcryptjs` needed.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { prehash } from 'ideal-auth';
|
|
224
|
+
|
|
225
|
+
// Bun native bcrypt (faster than bcryptjs — use prehash for 72-byte limit)
|
|
226
|
+
const hash: HashInstance = {
|
|
227
|
+
make: (password) => Bun.password.hash(prehash(password), { algorithm: 'bcrypt', cost: 12 }),
|
|
228
|
+
verify: (password, hash) => Bun.password.verify(prehash(password), hash),
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Bun argon2id (OWASP recommended — no prehash needed, no input length limit)
|
|
232
|
+
const hash: HashInstance = {
|
|
233
|
+
make: (password) => Bun.password.hash(password, { algorithm: 'argon2id', memoryCost: 65536, timeCost: 2 }),
|
|
234
|
+
verify: (password, hash) => Bun.password.verify(password, hash),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Node argon2 (requires: bun add argon2 — no prehash needed)
|
|
238
|
+
import argon2 from 'argon2';
|
|
239
|
+
const hash: HashInstance = {
|
|
240
|
+
make: (password) => argon2.hash(password),
|
|
241
|
+
verify: (password, hash) => argon2.verify(hash, password),
|
|
242
|
+
};
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Pass either to `createAuth`:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
const auth = createAuth({
|
|
249
|
+
secret: process.env.IDEAL_AUTH_SECRET!,
|
|
250
|
+
cookie: createCookieBridge(),
|
|
251
|
+
resolveUser: async (id) => db.user.findUnique({ where: { id } }),
|
|
252
|
+
hash, // createHash() or your custom HashInstance
|
|
253
|
+
resolveUserByCredentials: async (creds) =>
|
|
254
|
+
db.user.findUnique({ where: { email: creds.email } }),
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
218
258
|
---
|
|
219
259
|
|
|
220
260
|
### `createTokenVerifier(config): TokenVerifierInstance`
|