@luckystack/login 0.8.3 → 0.8.4
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/CHANGELOG.md +6 -0
- package/CLAUDE.md +5 -5
- package/README.md +12 -11
- package/docs/hooks.md +1 -1
- package/docs/password-reset.md +23 -31
- package/docs/session-management.md +2 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.4] - 2026-08-17
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Corrected password-reset/session documentation for hash-at-rest tokens, one-active-token-per-user invalidation, atomic shared-token consumption, current route paths, and pluggable session storage.
|
|
15
|
+
|
|
10
16
|
## [0.8.1] - 2026-07-27
|
|
11
17
|
|
|
12
18
|
### Fixed
|
package/CLAUDE.md
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
- **Credentials auth** (email + bcrypt-hashed password) with combined login/register dispatcher.
|
|
13
13
|
- **OAuth 2.0** via a pluggable provider registry. Built-in helpers for Google, GitHub, Discord, Facebook, Microsoft. Custom providers register as raw `OAuthProvider` objects.
|
|
14
|
-
- **Redis-backed sessions** with sliding expiration, CSRF token minting, single-session enforcement, and a swappable `SessionAdapter` (DynamoDB, Postgres, JWT-stateless, etc.).
|
|
14
|
+
- **Redis-backed default sessions** with sliding expiration, CSRF token minting, single-session enforcement, and a swappable `SessionAdapter` (DynamoDB, Postgres, JWT-stateless, etc.).
|
|
15
15
|
- **Lifecycle hooks**: `preLogin` / `postLogin`, `preRegister` / `postRegister`, `preLogout` / `postLogout`, `preSessionCreate` / `postSessionCreate`, `preSessionDelete` / `postSessionDelete`, `passwordResetRequested`, `passwordResetCompleted`, `passwordChanged`. All `pre*` hooks support stop-signals (abort the side-effect with a reason key).
|
|
16
16
|
- **Pluggable user store** via `UserAdapter` — decouples auth from a specific Prisma `User` schema.
|
|
17
17
|
- **Password-reset primitives** (`createPasswordResetToken`, `consumePasswordResetToken`, `updatePasswordHash`, `verifyPassword`) plus the framework-mode `sendPasswordResetEmail` orchestrator (requires the optional `@luckystack/email` peer).
|
|
18
18
|
- **Dynamic post-login redirect** resolver registry (per-user / per-tenant / per-provider OAuth landing pages). Relative resolver results inherit the trusted frontend `defaultUrl` origin, not the backend callback origin.
|
|
19
19
|
|
|
20
|
-
OAuth state is stored in Redis under `${projectName}-oauth-state:<provider>:<state>` with TTL `auth.oauthStateTtlSeconds`.
|
|
20
|
+
OAuth state is always stored in Redis under `${projectName}-oauth-state:<provider>:<state>` with TTL `auth.oauthStateTtlSeconds`. The default Redis session adapter stores sessions at `${projectName}-session:<token>` and active-tokens-per-user sets at `${projectName}-activeUsers:<userId>`; a custom `SessionAdapter` owns those storage details. Password-reset and email-change tokens are **hashed at rest** (0.2.0): only `sha256(token)` is stored as the Redis key (`${projectName}-pwreset:<sha256(token)>` / `${projectName}-email-change:<sha256(token)>`) via the `@luckystack/core` one-time-token primitive (`issueOneTimeToken` / `consumeOneTimeToken`). The raw token is returned to the caller exactly once for the emailed URL, so a leaked Redis keyspace can't be replayed to mint a reset or confirm an email change.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -139,7 +139,7 @@ All config keys live on `ProjectConfig` (from `@luckystack/core`). Resolved at c
|
|
|
139
139
|
| `FACEBOOK_CLIENT_ID` / `FACEBOOK_CLIENT_SECRET` | `facebookProvider` | OAuth app credentials. |
|
|
140
140
|
| `MICROSOFT_CLIENT_ID` / `MICROSOFT_CLIENT_SECRET` | `microsoftProvider` | OAuth app credentials. |
|
|
141
141
|
| `MICROSOFT_TENANT_ID` | `microsoftProvider` (optional) | Single-tenant Azure AD. Defaults to `'common'`. |
|
|
142
|
-
| (callback origin) | All OAuth `callbackUrl` builders | The `callbackUrl` is the BACKEND origin + `/auth/callback/<provider>`. The scaffold derives
|
|
142
|
+
| (callback origin) | All OAuth `callbackUrl` builders | The `callbackUrl` is the BACKEND origin + `/auth/callback/<provider>`. The scaffold derives dev from `config.ports.ts` (or the explicit CLI port) and production from `PUBLIC_URL`; generic consumers still fall back to `app.publicUrl`. No `DNS` env var is required by the framework OAuth contract. |
|
|
143
143
|
| `BCRYPT_ROUNDS` | `auth.bcryptRounds` | Surfaced through project config; salt rounds for credentials hashing. |
|
|
144
144
|
| `TOTP_ENCRYPTION_KEY` | TOTP at-rest encryption | Current primary write key; new rows are `enc:v2:<key-id>:...`. |
|
|
145
145
|
| `TOTP_ENCRYPTION_LEGACY_KEYS` | TOTP key rotation | JSON array of decrypt-only previous keys. Successful TOTP proofs lazily rewrite legacy/plaintext rows under the primary. |
|
|
@@ -180,7 +180,7 @@ All config keys live on `ProjectConfig` (from `@luckystack/core`). Resolved at c
|
|
|
180
180
|
| `bcryptjs` | hard runtime dep | Password hashing + comparison. |
|
|
181
181
|
| `validator` | hard runtime dep | Email validation + name escaping. |
|
|
182
182
|
| `dotenv` | hard runtime dep | Env loading at boot. |
|
|
183
|
-
| `@prisma/client` | **
|
|
183
|
+
| `@prisma/client` | **optional peer** | The default `UserAdapter` talks to `prisma.user`; register a custom `UserAdapter` when Prisma is not used. |
|
|
184
184
|
| `socket.io` | **required peer** | `logout` and session enforcement broadcast through the live `io` instance from `getIoInstance()`. |
|
|
185
185
|
| `@luckystack/email` | **optional peer** | Only required when `auth.forgotPassword === 'framework'`. Lazy-imported by `sendPasswordResetEmail` — every other API works without it. |
|
|
186
186
|
|
|
@@ -188,7 +188,7 @@ All config keys live on `ProjectConfig` (from `@luckystack/core`). Resolved at c
|
|
|
188
188
|
|
|
189
189
|
## Related links
|
|
190
190
|
|
|
191
|
-
-
|
|
191
|
+
- Repository-root contract: [`CLAUDE.md`](../../CLAUDE.md)
|
|
192
192
|
- Package README: [`./README.md`](./README.md)
|
|
193
193
|
- Architecture — auth lifecycle: [`/docs/ARCHITECTURE_AUTH.md`](../../docs/ARCHITECTURE_AUTH.md)
|
|
194
194
|
- Architecture — sessions (Redis layout, sliding expiration, single-session enforcement): [`/docs/ARCHITECTURE_SESSION.md`](../../docs/ARCHITECTURE_SESSION.md)
|
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# @luckystack/login
|
|
2
2
|
|
|
3
|
-
> Authentication for [LuckyStack](https://github.com/ItsLucky23/LuckyStack-v2). Credentials + OAuth (Google, GitHub, Facebook, Discord, Microsoft), Redis-backed sessions, single-session enforcement, and lifecycle hooks (`preLogin`, `preRegister`, `preLogout`, `preSessionCreate`, `preSessionDelete` and their `post*` counterparts).
|
|
3
|
+
> Authentication for [LuckyStack](https://github.com/ItsLucky23/LuckyStack-v2). Credentials + OAuth (Google, GitHub, Facebook, Discord, Microsoft), Redis-backed default sessions with a swappable session adapter, single-session enforcement, and lifecycle hooks (`preLogin`, `preRegister`, `preLogout`, `preSessionCreate`, `preSessionDelete` and their `post*` counterparts).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @luckystack/login
|
|
8
|
+
npm install @luckystack/login socket.io
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
The default user adapter uses Prisma; install optional peer `@prisma/client` when you use it. Framework-mode email flows additionally need optional peer `@luckystack/email`. Projects with custom `UserAdapter` and `SessionAdapter` implementations can use another data layer and session store.
|
|
12
|
+
|
|
11
13
|
## Quickstart
|
|
12
14
|
|
|
13
15
|
```ts
|
|
@@ -28,7 +30,7 @@ const result = await loginWithCredentials({ email, password });
|
|
|
28
30
|
// → { status: false, reason } on failure (including hook-stop with the hook's errorCode)
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
The default `redisSessionAdapter` stores sessions under `${PROJECT_NAME}-session:<token>` and applies sliding expiration (every authenticated read extends the TTL by `ProjectConfig.session.expiryDays`). Register a custom `SessionAdapter` when sessions should live in another store.
|
|
32
34
|
|
|
33
35
|
## Hooks
|
|
34
36
|
|
|
@@ -195,8 +197,8 @@ import {
|
|
|
195
197
|
| `loginCallback(pathname, req, res)` | OAuth state-exchange handler — wired to `/auth/callback/<provider>` by `@luckystack/server`. |
|
|
196
198
|
| `createOAuthState(providerName)` | Issue a CSRF state token (Redis, NX, TTL from project config). |
|
|
197
199
|
| `logout({ token, socket, userId })` | End a single socket's session. |
|
|
198
|
-
| `saveSession(token, user, newUser?)` | Write
|
|
199
|
-
| `getSession(token)` | Read + slide expiration. Dispatches `preSessionRefresh` / `postSessionRefresh` around the
|
|
200
|
+
| `saveSession(token, user, newUser?)` | Write through the active `SessionAdapter` + broadcast to existing connections. |
|
|
201
|
+
| `getSession(token)` | Read + slide expiration through the active adapter. Dispatches `preSessionRefresh` / `postSessionRefresh` around the adapter TTL refresh. |
|
|
200
202
|
| `deleteSession(token)` | Hard delete + clean up active-tokens set. |
|
|
201
203
|
| `getAllSessions()` | Admin utility — scans all sessions. |
|
|
202
204
|
| `revokeUserSessions(userId)` | Force-logout every active session for a user. |
|
|
@@ -204,6 +206,7 @@ import {
|
|
|
204
206
|
| `registerOAuthProviders(list)` / `getOAuthProviders()` / `isFullOAuthProvider(p)` | OAuth registry. |
|
|
205
207
|
| `googleProvider`, `githubProvider`, `discordProvider`, `facebookProvider`, `microsoftProvider`, `credentialsProvider` | Built-in provider factories. |
|
|
206
208
|
| `registerUserAdapter(adapter)` / `getUserAdapter()` / `isUserAdapterRegistered()` / `defaultPrismaUserAdapter` | Pluggable user store. |
|
|
209
|
+
| `registerSessionAdapter(adapter)` / `getSessionAdapter()` / `redisSessionAdapter` | Pluggable session storage; Redis is the default. |
|
|
207
210
|
| `registerPostLoginRedirect(resolver)` / `getPostLoginRedirect()` | Dynamic redirect resolution. |
|
|
208
211
|
| `createPasswordResetToken`, `consumePasswordResetToken`, `updatePasswordHash`, `verifyPassword`, `sendPasswordResetEmail` | Password-reset primitives. |
|
|
209
212
|
|
|
@@ -221,18 +224,16 @@ If your project has no such surfaces this is informational. The framework intent
|
|
|
221
224
|
## Related architecture docs
|
|
222
225
|
|
|
223
226
|
- [`docs/ARCHITECTURE_AUTH.md`](../../docs/ARCHITECTURE_AUTH.md) — OAuth + credentials lifecycle, allowed-origin checks, role guards.
|
|
224
|
-
- [`docs/ARCHITECTURE_SESSION.md`](../../docs/ARCHITECTURE_SESSION.md) — Redis layout, sliding expiration, single-session enforcement.
|
|
227
|
+
- [`docs/ARCHITECTURE_SESSION.md`](../../docs/ARCHITECTURE_SESSION.md) — session adapters, default Redis layout, sliding expiration, and single-session enforcement.
|
|
225
228
|
- [`docs/ARCHITECTURE_EMAIL.md`](../../docs/ARCHITECTURE_EMAIL.md) — forgot-password modes (`framework` / `custom` / `disabled`).
|
|
226
229
|
|
|
227
230
|
## Dependencies
|
|
228
231
|
|
|
229
232
|
- Runtime: `@luckystack/core`, `bcryptjs`, `validator`, `dotenv`
|
|
230
|
-
-
|
|
231
|
-
|
|
232
|
-
- `socket.io@^4.8.0`
|
|
233
|
-
- Optional peer: `@luckystack/email` — only required when `forgotPassword: 'framework'`. The package lazy-imports it; without it, the framework-mode flow is disabled but every other API works.
|
|
233
|
+
- Required peer: `socket.io@^4.8.0`
|
|
234
|
+
- Optional peers: `@prisma/client@^6.19.0` (default user adapter) and `@luckystack/email` (only when `forgotPassword: 'framework'`). The package lazy-imports optional peers; every other API works without them.
|
|
234
235
|
|
|
235
|
-
|
|
236
|
+
When using the default Prisma `UserAdapter`, your schema must include a `User` model with at least: `id`, `email`, `provider` (enum `PROVIDERS`), `password` (nullable), `name`, `avatar`, `avatarFallback`, `admin`, `language`. See [`prisma/schema.prisma`](../../prisma/schema.prisma) for the canonical shape. A custom `UserAdapter` and `SessionAdapter` can bind auth to another data layer and session store.
|
|
236
237
|
|
|
237
238
|
## License
|
|
238
239
|
|
package/docs/hooks.md
CHANGED
|
@@ -166,7 +166,7 @@ registerHook('postLogout', async ({ userId, token }) => {
|
|
|
166
166
|
{ token: string; user: BaseSessionLayout; persistent: boolean }
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
`persistent` is the inverse of `projectConfig.session.basedToken` — `true` means
|
|
169
|
+
`persistent` is the inverse of `projectConfig.session.basedToken` — `true` means cookie mode (the credential can survive a browser restart according to cookie expiry, and is shared across tabs); `false` means tab-scoped `sessionStorage` bearer mode. Audit handlers can use this to differentiate a "remember me" login from a one-shot one.
|
|
170
170
|
|
|
171
171
|
`user` is the freshly-sanitized session layout — password already stripped, avatar already resolved. Hooks should treat it as read-only; mutating it does NOT affect the saved session (the framework writes the original reference, but other hooks expect immutability).
|
|
172
172
|
|
package/docs/password-reset.md
CHANGED
|
@@ -18,18 +18,23 @@ This is the password lifecycle from "user clicks Forgot password" through "new p
|
|
|
18
18
|
|
|
19
19
|
The mode is read at call time via `getProjectConfig()`, so consumers can toggle it through `registerProjectConfig` without restarting. In practice, switching modes mid-process is uncommon — pick a mode at boot and stick with it.
|
|
20
20
|
|
|
21
|
-
## Token layout
|
|
21
|
+
## Token layout and hash-at-rest contract
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const handle = issueOneTimeToken('-pwreset', auth.passwordResetTtlSeconds, userId);
|
|
25
|
+
await handle.store();
|
|
26
|
+
|
|
27
|
+
// Redis stores only the hash-derived key:
|
|
28
|
+
// <projectName>-pwreset:<sha256(handle.token)> -> userId
|
|
29
|
+
return handle.token; // raw 64-character token, returned only for delivery
|
|
27
30
|
```
|
|
28
31
|
|
|
29
|
-
- **Entropy** — 256 bits via `randomBytes(32)
|
|
30
|
-
- **
|
|
31
|
-
- **Namespace** —
|
|
32
|
-
- **
|
|
32
|
+
- **Entropy** — 256 bits via `randomBytes(32)`, hex-encoded as a 64-character URL-safe token.
|
|
33
|
+
- **Hash at rest** — the raw token is never persisted. `issueOneTimeToken` derives a SHA-256 key suffix, so a Redis keyspace/backup leak does not yield a redeemable reset link.
|
|
34
|
+
- **Namespace** — `formatKey('-pwreset', sha256(token))`; the active project/tenant key formatter remains authoritative.
|
|
35
|
+
- **Single active token per user** — `${projectName}-pwreset-user:<userId>` points to the current hash key. Issuing a replacement deletes the previous outstanding token before storing the new one.
|
|
36
|
+
- **TTL** — both the hash record and per-user pointer use `auth.passwordResetTtlSeconds` (default 3600 seconds).
|
|
37
|
+
- **Storage boundary** — password-reset tokens intentionally use the core one-time-token Redis primitive; this is independent of the pluggable session-storage adapter.
|
|
33
38
|
|
|
34
39
|
## Primitives
|
|
35
40
|
|
|
@@ -39,9 +44,9 @@ await redis.set(key, userId, 'EX', auth.passwordResetTtlSeconds);
|
|
|
39
44
|
createPasswordResetToken(userId: string): Promise<string>;
|
|
40
45
|
```
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
Invalidate the user's previous outstanding reset token, mint/store a new hash-at-rest token, persist the per-user pointer, and return the raw token. The caller is responsible for delivering it—normally in an `${appPublicUrl}/reset-password?token=<token>` URL.
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
The promise rejects when Redis cannot read/delete/store the token records. Framework-mode callers normalize that failure; custom callers should use the standard `tryCatch` helper when they need a typed degradation path.
|
|
45
50
|
|
|
46
51
|
### `consumePasswordResetToken(token)`
|
|
47
52
|
|
|
@@ -49,23 +54,9 @@ Always succeeds (returns the token string). If Redis is down, the underlying `re
|
|
|
49
54
|
consumePasswordResetToken(token: string): Promise<string | null>;
|
|
50
55
|
```
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
const txResult = await redis.multi().get(key).del(key).exec();
|
|
56
|
-
const [getErr, value] = txResult[0];
|
|
57
|
-
if (getErr) return null;
|
|
58
|
-
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Returns the bound `userId` on success, `null` on:
|
|
62
|
-
|
|
63
|
-
- Empty or non-string `token` argument.
|
|
64
|
-
- Token expired (Redis returns null for the GET).
|
|
65
|
-
- Token already consumed (the DEL on a prior call already removed it).
|
|
66
|
-
- The transaction failed entirely (Redis network blip).
|
|
57
|
+
Delegates to `consumeOneTimeToken('-pwreset', token)`. The shared primitive hashes the supplied raw token to derive the Redis key and performs one `MULTI` containing `GET` + `DEL`. It returns the bound `userId` only when both commands succeed.
|
|
67
58
|
|
|
68
|
-
|
|
59
|
+
Returns `null` for an empty/malformed token, missing or expired hash record, an already-consumed token, or any transaction/DEL failure. A concurrent second redemption sees no value and fails closed, providing at-most-once consumption.
|
|
69
60
|
|
|
70
61
|
### `updatePasswordHash(userId, plaintext)`
|
|
71
62
|
|
|
@@ -80,10 +71,11 @@ const reason = validatePassword(plaintext);
|
|
|
80
71
|
if (reason) throw new PasswordPolicyError(reason);
|
|
81
72
|
const salt = await bcrypt.genSalt(getProjectConfig().auth.bcryptRounds);
|
|
82
73
|
const hashedPassword = await bcrypt.hash(plaintext, salt);
|
|
83
|
-
|
|
74
|
+
const patch: Record<string, unknown> = { password: hashedPassword };
|
|
75
|
+
await getUserAdapter().update(userId, patch);
|
|
84
76
|
```
|
|
85
77
|
|
|
86
|
-
Throws `PasswordPolicyError` on policy violation.
|
|
78
|
+
Throws `PasswordPolicyError` on policy violation. `UserAdapter.update` accepts a loose patch because custom user stores may expose different optional credential fields; no transport-typing cast is needed.
|
|
87
79
|
|
|
88
80
|
Note this primitive deliberately does NOT consume a reset token. The reset-password completion API is responsible for the sequence:
|
|
89
81
|
|
|
@@ -209,9 +201,9 @@ const { sendEmail, renderEmailLayout } = await (
|
|
|
209
201
|
The framework-mode flow has two HTTP endpoints (wired by `@luckystack/server` via the file-based router):
|
|
210
202
|
|
|
211
203
|
```
|
|
212
|
-
1. POST /api/
|
|
213
|
-
|
|
214
|
-
2. POST /api/
|
|
204
|
+
1. POST /api/reset-password/sendReset/v1 → sendPasswordResetEmail({ email })
|
|
205
|
+
└ token hash now in Redis; raw token is emailed
|
|
206
|
+
2. POST /api/reset-password/confirmReset/v1 → consumePasswordResetToken(token)
|
|
215
207
|
└ updatePasswordHash(userId, newPassword)
|
|
216
208
|
└ revokeUserSessions(userId, currentToken?)
|
|
217
209
|
└ dispatchHook('passwordResetCompleted', { userId, revokedOtherSessions })
|
|
@@ -11,7 +11,8 @@ Deep-dive on `saveSession` / `getSession` / `deleteSession`, sliding expiration,
|
|
|
11
11
|
| `${projectName}-session:<token>` | string | JSON-encoded `BaseSessionLayout` (id, email, name, csrfToken, custom fields). |
|
|
12
12
|
| `${projectName}-activeUsers:<userId>` | set | Tokens currently associated with this user. Read by single-session enforcement. |
|
|
13
13
|
| `${projectName}-oauth-state:<provider>:<state>` | string | `'1'` sentinel + TTL. Consumed atomically on OAuth callback. (See `login.ts`.) |
|
|
14
|
-
| `${projectName}-pwreset:<token>`
|
|
14
|
+
| `${projectName}-pwreset:<sha256(token)>` | string | `userId` bound to the hash-at-rest one-time token; raw token is never persisted. |
|
|
15
|
+
| `${projectName}-pwreset-user:<userId>` | string | Hash-key pointer for the user's only active reset token; replacement issuance invalidates the prior key. |
|
|
15
16
|
|
|
16
17
|
`projectName` comes from `getProjectName()` in `@luckystack/core` — a single source of truth so the namespace is identical across sessions, activeUsers, OAuth state, password reset, and rate-limit.
|
|
17
18
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luckystack/login",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": true
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
-
"description": "Authentication for LuckyStack: credentials + OAuth (Google/GitHub/Facebook/Discord), Redis-backed sessions, single-session enforcement, lifecycle hooks. Pairs with @luckystack/core.",
|
|
10
|
+
"description": "Authentication for LuckyStack: credentials + OAuth (Google/GitHub/Facebook/Discord/Microsoft/custom), Redis-backed sessions, single-session enforcement, password reset, 2FA, lifecycle hooks, and pluggable user/session adapters. Pairs with @luckystack/core.",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"author": "Mathijs van Melick <mathijsvanmelick3@gmail.com>",
|
|
13
13
|
"repository": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"test": "vitest run"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@luckystack/core": "^0.8.
|
|
62
|
+
"@luckystack/core": "^0.8.4",
|
|
63
63
|
"bcryptjs": "^3.0.0",
|
|
64
64
|
"dotenv": "^17.0.0",
|
|
65
65
|
"validator": "^13.15.35"
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"peerDependencies": {
|
|
68
68
|
"@prisma/client": "^6.19.0",
|
|
69
69
|
"socket.io": "^4.8.0",
|
|
70
|
-
"@luckystack/email": "^0.8.
|
|
70
|
+
"@luckystack/email": "^0.8.4"
|
|
71
71
|
},
|
|
72
72
|
"peerDependenciesMeta": {
|
|
73
73
|
"@prisma/client": {
|