my-crud-lib 2.0.0 → 2.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/CHANGELOG.md +45 -0
- package/README.md +41 -1
- package/RELEASE.md +4 -0
- package/dist/adapter-prisma.d.ts +1 -1
- package/dist/adapter-prisma.js +1 -1
- package/dist/adapters/prisma.d.ts +5 -0
- package/dist/adapters/prisma.js +125 -0
- package/dist/auth.d.ts +3 -3
- package/dist/auth.js +1 -1
- package/dist/core/ports/user.repo.d.ts +2 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/modules/auth/auth.controller.js +73 -1
- package/dist/modules/auth/auth.schemas.d.ts +35 -0
- package/dist/modules/auth/auth.schemas.js +13 -0
- package/dist/modules/auth/auth.service.d.ts +19 -1
- package/dist/modules/auth/auth.service.js +219 -8
- package/dist/modules/auth/auth.types.d.ts +130 -1
- package/dist/modules/user/user.types.d.ts +1 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/utils/jwt.d.ts +2 -0
- package/docs/auth-extensions.md +120 -0
- package/docs/github-self-hosted-runner.md +76 -0
- package/docs/migration-v2.md +131 -0
- package/examples/express-prisma/prisma/schema.prisma +69 -8
- package/package.json +5 -5
- package/prisma/schema.prisma +69 -8
- package/prisma.config.ts +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `my-crud-lib` are documented here.
|
|
4
|
+
|
|
5
|
+
This project follows semantic versioning. Breaking changes are called out explicitly and should be reviewed before upgrading.
|
|
6
|
+
|
|
7
|
+
## 2.1.0 - Unreleased
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added a repeatable changelog and release-note workflow.
|
|
12
|
+
- Added a v2 migration guide for applications upgrading from the pre-v2 API shape.
|
|
13
|
+
- Added `prisma.config.ts` so Prisma CLI configuration no longer relies on the deprecated `package.json#prisma` field.
|
|
14
|
+
- Added self-hosted GitHub Actions runner documentation and configured CI to target the `local-ci` runner label.
|
|
15
|
+
- Added optional persistent refresh token rotation/revocation ports and Prisma adapter.
|
|
16
|
+
- Added optional password reset request/confirm service methods, routes, hooks, token repository port, and Prisma adapter.
|
|
17
|
+
- Added optional email verification request/confirm service methods, routes, hooks, token repository port, and Prisma adapter.
|
|
18
|
+
- Added provider-agnostic OAuth account linking service methods, port, and Prisma adapter.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Bumped the package version to `2.1.0`.
|
|
23
|
+
- Expanded the starter Prisma schema with optional auth extension storage models.
|
|
24
|
+
|
|
25
|
+
## 2.0.0 - 2026-05-14
|
|
26
|
+
|
|
27
|
+
### Breaking Changes
|
|
28
|
+
|
|
29
|
+
- Auth routes are now dependency-injected. `createAuthRouter()` requires a `userRepo` dependency instead of constructing Prisma access internally.
|
|
30
|
+
- `createLibrary()` now receives application dependencies separately from route configuration.
|
|
31
|
+
- Self-registration creates `USER` accounts by default. Applications must create `ADMIN` users intentionally through their own seed or admin workflow.
|
|
32
|
+
- Public imports were consolidated around documented package entry points: `my-crud-lib`, `my-crud-lib/auth`, `my-crud-lib/user`, `my-crud-lib/schemas`, `my-crud-lib/middleware`, `my-crud-lib/adapter-prisma`, and `my-crud-lib/adapters/prisma`.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Added public Express router factories for auth and user/profile CRUD.
|
|
37
|
+
- Added `UserRepo` as the core persistence port.
|
|
38
|
+
- Added Prisma adapter exports.
|
|
39
|
+
- Added smoke tests for public package exports, auth safety defaults, and adapter-driven auth service behavior.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Prisma is optional for consumers that provide a custom repository adapter.
|
|
44
|
+
- Auth behavior now strips `passwordHash` from service responses.
|
|
45
|
+
- JWT configuration validates `JWT_SECRET` before signing or verifying tokens.
|
package/README.md
CHANGED
|
@@ -9,10 +9,15 @@ The package currently provides:
|
|
|
9
9
|
|
|
10
10
|
- Express routers for auth and user CRUD.
|
|
11
11
|
- JWT access and refresh token helpers.
|
|
12
|
+
- Optional persistent refresh token rotation and revocation.
|
|
13
|
+
- Optional password reset and email verification hooks.
|
|
14
|
+
- Provider-agnostic OAuth account linking extension points.
|
|
12
15
|
- Zod schemas for request validation.
|
|
13
16
|
- A `UserRepo` port plus a Prisma adapter.
|
|
14
17
|
- Convenience setup helpers for small Express APIs.
|
|
15
18
|
|
|
19
|
+
For advanced auth flows, see [docs/auth-extensions.md](docs/auth-extensions.md). For v1 to v2 upgrades, see [docs/migration-v2.md](docs/migration-v2.md). Release history and breaking changes are tracked in [CHANGELOG.md](CHANGELOG.md).
|
|
20
|
+
|
|
16
21
|
## Installation
|
|
17
22
|
|
|
18
23
|
```bash
|
|
@@ -72,6 +77,11 @@ With the `/api` prefix, the mounted routes include:
|
|
|
72
77
|
- `POST /api/auth/register`
|
|
73
78
|
- `POST /api/auth/login`
|
|
74
79
|
- `POST /api/auth/refresh`
|
|
80
|
+
- `POST /api/auth/logout`
|
|
81
|
+
- `POST /api/auth/password-reset/request`
|
|
82
|
+
- `POST /api/auth/password-reset/confirm`
|
|
83
|
+
- `POST /api/auth/email-verification/request`
|
|
84
|
+
- `POST /api/auth/email-verification/confirm`
|
|
75
85
|
- `GET /api/auth/me`
|
|
76
86
|
- `GET /api/users`
|
|
77
87
|
- `GET /api/users/me`
|
|
@@ -206,7 +216,13 @@ export interface UserRepo {
|
|
|
206
216
|
The Prisma adapter is available from both import paths:
|
|
207
217
|
|
|
208
218
|
```ts
|
|
209
|
-
import {
|
|
219
|
+
import {
|
|
220
|
+
makePrismaEmailVerificationTokenRepo,
|
|
221
|
+
makePrismaOAuthAccountRepo,
|
|
222
|
+
makePrismaPasswordResetTokenRepo,
|
|
223
|
+
makePrismaRefreshTokenRepo,
|
|
224
|
+
makePrismaUserRepo,
|
|
225
|
+
} from "my-crud-lib/adapter-prisma";
|
|
210
226
|
// or
|
|
211
227
|
import { makePrismaUserRepo } from "my-crud-lib/adapters/prisma";
|
|
212
228
|
```
|
|
@@ -219,10 +235,34 @@ import { createAuthRouter } from "my-crud-lib/auth";
|
|
|
219
235
|
app.use("/auth", createAuthRouter({ userRepo }));
|
|
220
236
|
```
|
|
221
237
|
|
|
238
|
+
Optional auth extensions use additional ports:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
app.use(
|
|
242
|
+
"/auth",
|
|
243
|
+
createAuthRouter({
|
|
244
|
+
userRepo,
|
|
245
|
+
refreshTokenRepo,
|
|
246
|
+
passwordResetTokenRepo,
|
|
247
|
+
emailVerificationTokenRepo,
|
|
248
|
+
oauthAccountRepo,
|
|
249
|
+
async sendPasswordReset({ user, token }) {
|
|
250
|
+
await emailProvider.sendPasswordReset(user.email, token);
|
|
251
|
+
},
|
|
252
|
+
async sendEmailVerification({ user, token }) {
|
|
253
|
+
await emailProvider.sendVerification(user.email, token);
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
These dependencies are optional. Without them, the existing stateless refresh-token flow remains available and password reset, email verification, and OAuth methods report that they are not configured.
|
|
260
|
+
|
|
222
261
|
## Build Checks
|
|
223
262
|
|
|
224
263
|
```bash
|
|
225
264
|
npm run build
|
|
265
|
+
npm test
|
|
226
266
|
npm run smoke:exports
|
|
227
267
|
npm run smoke:auth-hardening
|
|
228
268
|
npm run smoke:auth-service
|
package/RELEASE.md
CHANGED
|
@@ -5,7 +5,10 @@ Use this checklist before publishing `my-crud-lib` to npm.
|
|
|
5
5
|
## Preflight
|
|
6
6
|
|
|
7
7
|
- Confirm `package.json` version is the intended release version.
|
|
8
|
+
- Add an entry to `CHANGELOG.md` under the target version.
|
|
9
|
+
- Move `CHANGELOG.md` entries from `Unreleased` to the release date before publishing.
|
|
8
10
|
- Confirm `README.md` examples match tested public imports.
|
|
11
|
+
- Confirm migration notes are linked when the release contains breaking changes.
|
|
9
12
|
- Confirm `LICENSE`, `README.md`, `examples`, `dist`, and `prisma/schema.prisma` are included in the package.
|
|
10
13
|
- Review open issues for release blockers.
|
|
11
14
|
|
|
@@ -28,5 +31,6 @@ npm publish --access public
|
|
|
28
31
|
## After Publish
|
|
29
32
|
|
|
30
33
|
- Create a GitHub release or tag for the published version.
|
|
34
|
+
- Use `CHANGELOG.md` as the release body, and include migration links for breaking releases.
|
|
31
35
|
- Confirm the npm page shows repository, license, README, examples, and keywords.
|
|
32
36
|
- Smoke test installation in a fresh temporary project.
|
package/dist/adapter-prisma.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { makePrismaUserRepo } from './adapters/prisma.js';
|
|
1
|
+
export { makePrismaEmailVerificationTokenRepo, makePrismaOAuthAccountRepo, makePrismaPasswordResetTokenRepo, makePrismaRefreshTokenRepo, makePrismaUserRepo, } from './adapters/prisma.js';
|
package/dist/adapter-prisma.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { makePrismaUserRepo } from './adapters/prisma.js';
|
|
1
|
+
export { makePrismaEmailVerificationTokenRepo, makePrismaOAuthAccountRepo, makePrismaPasswordResetTokenRepo, makePrismaRefreshTokenRepo, makePrismaUserRepo, } from './adapters/prisma.js';
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { PrismaClient } from '@prisma/client';
|
|
2
2
|
import type { UserRepo } from '../core/ports/user.repo.js';
|
|
3
|
+
import type { EmailVerificationTokenRepo, OAuthAccountRepo, PasswordResetTokenRepo, RefreshTokenRepo } from '../modules/auth/auth.types.js';
|
|
3
4
|
export declare function makePrismaUserRepo(prisma: PrismaClient): UserRepo;
|
|
5
|
+
export declare function makePrismaRefreshTokenRepo(prisma: PrismaClient): RefreshTokenRepo;
|
|
6
|
+
export declare function makePrismaPasswordResetTokenRepo(prisma: PrismaClient): PasswordResetTokenRepo;
|
|
7
|
+
export declare function makePrismaEmailVerificationTokenRepo(prisma: PrismaClient): EmailVerificationTokenRepo;
|
|
8
|
+
export declare function makePrismaOAuthAccountRepo(prisma: PrismaClient): OAuthAccountRepo;
|
package/dist/adapters/prisma.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const dateOrNow = (value) => value ?? new Date();
|
|
1
2
|
export function makePrismaUserRepo(prisma) {
|
|
2
3
|
return {
|
|
3
4
|
async count({ role, search }) {
|
|
@@ -128,5 +129,129 @@ export function makePrismaUserRepo(prisma) {
|
|
|
128
129
|
},
|
|
129
130
|
});
|
|
130
131
|
},
|
|
132
|
+
async updatePassword(id, passwordHash) {
|
|
133
|
+
return prisma.user.update({
|
|
134
|
+
where: { id: id },
|
|
135
|
+
data: { passwordHash },
|
|
136
|
+
select: {
|
|
137
|
+
id: true,
|
|
138
|
+
email: true,
|
|
139
|
+
name: true,
|
|
140
|
+
role: true,
|
|
141
|
+
createdAt: true,
|
|
142
|
+
updatedAt: true,
|
|
143
|
+
profile: { select: { bio: true, avatarUrl: true } },
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
async markEmailVerified(id, verifiedAt = new Date()) {
|
|
148
|
+
return prisma.user.update({
|
|
149
|
+
where: { id: id },
|
|
150
|
+
data: { emailVerifiedAt: verifiedAt },
|
|
151
|
+
select: {
|
|
152
|
+
id: true,
|
|
153
|
+
email: true,
|
|
154
|
+
name: true,
|
|
155
|
+
role: true,
|
|
156
|
+
emailVerifiedAt: true,
|
|
157
|
+
createdAt: true,
|
|
158
|
+
updatedAt: true,
|
|
159
|
+
profile: { select: { bio: true, avatarUrl: true } },
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function makePrismaRefreshTokenRepo(prisma) {
|
|
166
|
+
return {
|
|
167
|
+
create(input) {
|
|
168
|
+
return prisma.refreshToken.create({ data: input });
|
|
169
|
+
},
|
|
170
|
+
findById(id) {
|
|
171
|
+
return prisma.refreshToken.findUnique({ where: { id } });
|
|
172
|
+
},
|
|
173
|
+
async revoke(id, input = {}) {
|
|
174
|
+
await prisma.refreshToken.update({
|
|
175
|
+
where: { id },
|
|
176
|
+
data: {
|
|
177
|
+
revokedAt: dateOrNow(input.revokedAt),
|
|
178
|
+
replacedByTokenId: input.replacedByTokenId ?? undefined,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
async revokeFamily(familyId, input = {}) {
|
|
183
|
+
await prisma.refreshToken.updateMany({
|
|
184
|
+
where: { familyId, revokedAt: null },
|
|
185
|
+
data: { revokedAt: dateOrNow(input.revokedAt) },
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function makePrismaPasswordResetTokenRepo(prisma) {
|
|
191
|
+
return {
|
|
192
|
+
create(input) {
|
|
193
|
+
return prisma.passwordResetToken.create({ data: input });
|
|
194
|
+
},
|
|
195
|
+
findById(id) {
|
|
196
|
+
return prisma.passwordResetToken.findUnique({ where: { id } });
|
|
197
|
+
},
|
|
198
|
+
async markUsed(id, input = {}) {
|
|
199
|
+
await prisma.passwordResetToken.update({
|
|
200
|
+
where: { id },
|
|
201
|
+
data: { usedAt: dateOrNow(input.usedAt) },
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
async revoke(id, input = {}) {
|
|
205
|
+
await prisma.passwordResetToken.update({
|
|
206
|
+
where: { id },
|
|
207
|
+
data: { revokedAt: dateOrNow(input.revokedAt) },
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export function makePrismaEmailVerificationTokenRepo(prisma) {
|
|
213
|
+
return {
|
|
214
|
+
create(input) {
|
|
215
|
+
return prisma.emailVerificationToken.create({ data: input });
|
|
216
|
+
},
|
|
217
|
+
findById(id) {
|
|
218
|
+
return prisma.emailVerificationToken.findUnique({ where: { id } });
|
|
219
|
+
},
|
|
220
|
+
async markUsed(id, input = {}) {
|
|
221
|
+
await prisma.emailVerificationToken.update({
|
|
222
|
+
where: { id },
|
|
223
|
+
data: { usedAt: dateOrNow(input.usedAt) },
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
async revoke(id, input = {}) {
|
|
227
|
+
await prisma.emailVerificationToken.update({
|
|
228
|
+
where: { id },
|
|
229
|
+
data: { revokedAt: dateOrNow(input.revokedAt) },
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
export function makePrismaOAuthAccountRepo(prisma) {
|
|
235
|
+
return {
|
|
236
|
+
findByProviderAccount(provider, providerAccountId) {
|
|
237
|
+
return prisma.oauthAccount.findUnique({
|
|
238
|
+
where: { provider_providerAccountId: { provider, providerAccountId } },
|
|
239
|
+
});
|
|
240
|
+
},
|
|
241
|
+
findByUserAndProvider(userId, provider) {
|
|
242
|
+
return prisma.oauthAccount.findFirst({
|
|
243
|
+
where: { userId: userId, provider },
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
create(input) {
|
|
247
|
+
return prisma.oauthAccount.create({
|
|
248
|
+
data: {
|
|
249
|
+
provider: input.provider,
|
|
250
|
+
providerAccountId: input.providerAccountId,
|
|
251
|
+
userId: input.userId,
|
|
252
|
+
email: input.email ?? null,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
},
|
|
131
256
|
};
|
|
132
257
|
}
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { createAuthRouter } from './modules/auth/auth.controller.js';
|
|
2
2
|
export { DEFAULT_REGISTER_ROLE, resolveRegisterRole } from './modules/auth/auth.defaults.js';
|
|
3
3
|
export { makeAuthService } from './modules/auth/auth.service.js';
|
|
4
|
-
export { loginSchema, registerSchema } from './modules/auth/auth.schemas.js';
|
|
5
|
-
export type { AuthResult, AuthServiceDeps, AuthTokens, AuthUser, AuthUserRepo } from './modules/auth/auth.types.js';
|
|
6
|
-
export type { LoginInput, RegisterInput } from './modules/auth/auth.schemas.js';
|
|
4
|
+
export { emailVerificationConfirmSchema, emailVerificationRequestSchema, loginSchema, passwordResetConfirmSchema, passwordResetRequestSchema, registerSchema, } from './modules/auth/auth.schemas.js';
|
|
5
|
+
export type { AuthResult, AuthServiceDeps, AuthTokens, AuthUser, AuthUserRepo, EmailVerificationConfirmInput, EmailVerificationRequestInput, EmailVerificationTokenRecord, EmailVerificationTokenRepo, OAuthAccount, OAuthAccountRepo, OAuthProviderProfile, PasswordResetConfirmInput, PasswordResetRequestInput, PasswordResetTokenRecord, PasswordResetTokenRepo, RefreshTokenRecord, RefreshTokenRepo, } from './modules/auth/auth.types.js';
|
|
6
|
+
export type { EmailVerificationConfirmInput as EmailVerificationConfirmSchemaInput, EmailVerificationRequestInput as EmailVerificationRequestSchemaInput, LoginInput, PasswordResetConfirmInput as PasswordResetConfirmSchemaInput, PasswordResetRequestInput as PasswordResetRequestSchemaInput, RegisterInput, } from './modules/auth/auth.schemas.js';
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { createAuthRouter } from './modules/auth/auth.controller.js';
|
|
2
2
|
export { DEFAULT_REGISTER_ROLE, resolveRegisterRole } from './modules/auth/auth.defaults.js';
|
|
3
3
|
export { makeAuthService } from './modules/auth/auth.service.js';
|
|
4
|
-
export { loginSchema, registerSchema } from './modules/auth/auth.schemas.js';
|
|
4
|
+
export { emailVerificationConfirmSchema, emailVerificationRequestSchema, loginSchema, passwordResetConfirmSchema, passwordResetRequestSchema, registerSchema, } from './modules/auth/auth.schemas.js';
|
|
@@ -31,4 +31,6 @@ export interface UserRepo {
|
|
|
31
31
|
bio?: string | null;
|
|
32
32
|
avatarUrl?: string | null;
|
|
33
33
|
}): Promise<UserListItem>;
|
|
34
|
+
updatePassword?(id: number | string, passwordHash: string): Promise<UserListItem | void>;
|
|
35
|
+
markEmailVerified?(id: number | string, verifiedAt?: Date): Promise<UserListItem | void>;
|
|
34
36
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,12 @@ export { createUserRouter } from './modules/user/user.controller.js';
|
|
|
5
5
|
export { createAuthRouter } from './modules/auth/auth.controller.js';
|
|
6
6
|
export { DEFAULT_REGISTER_ROLE, resolveRegisterRole } from './modules/auth/auth.defaults.js';
|
|
7
7
|
export { makeAuthService } from './modules/auth/auth.service.js';
|
|
8
|
-
export {
|
|
9
|
-
export type { AuthResult, AuthServiceDeps, AuthTokens, AuthUser, AuthUserRepo } from './modules/auth/auth.types.js';
|
|
8
|
+
export { emailVerificationConfirmSchema, emailVerificationRequestSchema, loginSchema, passwordResetConfirmSchema, passwordResetRequestSchema, registerSchema, } from './modules/auth/auth.schemas.js';
|
|
9
|
+
export type { AuthResult, AuthServiceDeps, AuthTokens, AuthUser, AuthUserRepo, EmailVerificationConfirmInput, EmailVerificationRequestInput, EmailVerificationTokenRecord, EmailVerificationTokenRepo, OAuthAccount, OAuthAccountRepo, OAuthProviderProfile, PasswordResetConfirmInput, PasswordResetRequestInput, PasswordResetTokenRecord, PasswordResetTokenRepo, RefreshTokenRecord, RefreshTokenRepo, } from './modules/auth/auth.types.js';
|
|
10
10
|
export { SortEnum, adminCreateUserSchema, adminUpdateUserSchema, listUsersQuerySchema, updateMeSchema, } from './modules/user/user.schemas.js';
|
|
11
11
|
export { isAuth, type AuthRequest } from './middleware/isAuth.js';
|
|
12
12
|
export { hasRole, isSelfOrAdmin } from './middleware/hasRole.js';
|
|
13
|
-
export { makePrismaUserRepo } from './adapters/prisma.js';
|
|
13
|
+
export { makePrismaEmailVerificationTokenRepo, makePrismaOAuthAccountRepo, makePrismaPasswordResetTokenRepo, makePrismaRefreshTokenRepo, makePrismaUserRepo, } from './adapters/prisma.js';
|
|
14
14
|
import type { UserRepo } from './core/ports/user.repo.js';
|
|
15
15
|
import type { AuthServiceDeps } from './modules/auth/auth.types.js';
|
|
16
16
|
export type LibraryConfig = {
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,11 @@ export { createUserRouter } from './modules/user/user.controller.js';
|
|
|
5
5
|
export { createAuthRouter } from './modules/auth/auth.controller.js';
|
|
6
6
|
export { DEFAULT_REGISTER_ROLE, resolveRegisterRole } from './modules/auth/auth.defaults.js';
|
|
7
7
|
export { makeAuthService } from './modules/auth/auth.service.js';
|
|
8
|
-
export {
|
|
8
|
+
export { emailVerificationConfirmSchema, emailVerificationRequestSchema, loginSchema, passwordResetConfirmSchema, passwordResetRequestSchema, registerSchema, } from './modules/auth/auth.schemas.js';
|
|
9
9
|
export { SortEnum, adminCreateUserSchema, adminUpdateUserSchema, listUsersQuerySchema, updateMeSchema, } from './modules/user/user.schemas.js';
|
|
10
10
|
export { isAuth } from './middleware/isAuth.js';
|
|
11
11
|
export { hasRole, isSelfOrAdmin } from './middleware/hasRole.js';
|
|
12
|
-
export { makePrismaUserRepo } from './adapters/prisma.js';
|
|
12
|
+
export { makePrismaEmailVerificationTokenRepo, makePrismaOAuthAccountRepo, makePrismaPasswordResetTokenRepo, makePrismaRefreshTokenRepo, makePrismaUserRepo, } from './adapters/prisma.js';
|
|
13
13
|
import { createAuthRouter } from './modules/auth/auth.controller.js';
|
|
14
14
|
import { createUserRouter } from './modules/user/user.controller.js';
|
|
15
15
|
function normalizePrefix(prefix) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { isAuth } from '../../middleware/isAuth.js';
|
|
3
|
-
import { loginSchema, registerSchema } from './auth.schemas.js';
|
|
3
|
+
import { emailVerificationConfirmSchema, emailVerificationRequestSchema, loginSchema, passwordResetConfirmSchema, passwordResetRequestSchema, registerSchema, } from './auth.schemas.js';
|
|
4
4
|
import { makeAuthService } from './auth.service.js';
|
|
5
5
|
export function createAuthRouter(deps) {
|
|
6
6
|
const router = Router();
|
|
@@ -45,6 +45,78 @@ export function createAuthRouter(deps) {
|
|
|
45
45
|
return res.status(401).json({ error: 'Invalid or expired refresh token' });
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
+
router.post('/logout', async (req, res) => {
|
|
49
|
+
try {
|
|
50
|
+
const { refreshToken } = req.body;
|
|
51
|
+
if (!refreshToken)
|
|
52
|
+
return res.status(400).json({ error: 'Missing refreshToken' });
|
|
53
|
+
await service.revokeRefreshToken(refreshToken);
|
|
54
|
+
return res.status(204).send();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return res.status(401).json({ error: 'Invalid or expired refresh token' });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
router.post('/password-reset/request', async (req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
const data = passwordResetRequestSchema.parse(req.body);
|
|
63
|
+
await service.requestPasswordReset(data);
|
|
64
|
+
return res.status(202).json({ ok: true });
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
if (err?.message === 'PASSWORD_RESET_UNSUPPORTED')
|
|
68
|
+
return res.status(501).json({ error: 'Password reset is not configured' });
|
|
69
|
+
if (err?.issues)
|
|
70
|
+
return res.status(400).json({ error: 'ValidationError', details: err.issues });
|
|
71
|
+
return res.status(500).json({ error: 'InternalError' });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
router.post('/password-reset/confirm', async (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
const data = passwordResetConfirmSchema.parse(req.body);
|
|
77
|
+
await service.confirmPasswordReset(data);
|
|
78
|
+
return res.json({ ok: true });
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (err?.message === 'PASSWORD_RESET_UNSUPPORTED')
|
|
82
|
+
return res.status(501).json({ error: 'Password reset is not configured' });
|
|
83
|
+
if (err?.message === 'INVALID_PASSWORD_RESET_TOKEN')
|
|
84
|
+
return res.status(400).json({ error: 'Invalid or expired password reset token' });
|
|
85
|
+
if (err?.issues)
|
|
86
|
+
return res.status(400).json({ error: 'ValidationError', details: err.issues });
|
|
87
|
+
return res.status(500).json({ error: 'InternalError' });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
router.post('/email-verification/request', async (req, res) => {
|
|
91
|
+
try {
|
|
92
|
+
const data = emailVerificationRequestSchema.parse(req.body);
|
|
93
|
+
await service.requestEmailVerification(data);
|
|
94
|
+
return res.status(202).json({ ok: true });
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
if (err?.message === 'EMAIL_VERIFICATION_UNSUPPORTED')
|
|
98
|
+
return res.status(501).json({ error: 'Email verification is not configured' });
|
|
99
|
+
if (err?.issues)
|
|
100
|
+
return res.status(400).json({ error: 'ValidationError', details: err.issues });
|
|
101
|
+
return res.status(500).json({ error: 'InternalError' });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
router.post('/email-verification/confirm', async (req, res) => {
|
|
105
|
+
try {
|
|
106
|
+
const data = emailVerificationConfirmSchema.parse(req.body);
|
|
107
|
+
const result = await service.confirmEmailVerification(data);
|
|
108
|
+
return res.json(result);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (err?.message === 'EMAIL_VERIFICATION_UNSUPPORTED')
|
|
112
|
+
return res.status(501).json({ error: 'Email verification is not configured' });
|
|
113
|
+
if (err?.message === 'INVALID_EMAIL_VERIFICATION_TOKEN')
|
|
114
|
+
return res.status(400).json({ error: 'Invalid or expired email verification token' });
|
|
115
|
+
if (err?.issues)
|
|
116
|
+
return res.status(400).json({ error: 'ValidationError', details: err.issues });
|
|
117
|
+
return res.status(500).json({ error: 'InternalError' });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
48
120
|
router.get('/me', isAuth, async (req, res) => {
|
|
49
121
|
const userId = req.user.id;
|
|
50
122
|
const me = await service.getMe(userId);
|
|
@@ -22,5 +22,40 @@ export declare const loginSchema: z.ZodObject<{
|
|
|
22
22
|
email: string;
|
|
23
23
|
password: string;
|
|
24
24
|
}>;
|
|
25
|
+
export declare const passwordResetRequestSchema: z.ZodObject<{
|
|
26
|
+
email: z.ZodString;
|
|
27
|
+
}, "strip", z.ZodTypeAny, {
|
|
28
|
+
email: string;
|
|
29
|
+
}, {
|
|
30
|
+
email: string;
|
|
31
|
+
}>;
|
|
32
|
+
export declare const passwordResetConfirmSchema: z.ZodObject<{
|
|
33
|
+
token: z.ZodString;
|
|
34
|
+
password: z.ZodString;
|
|
35
|
+
}, "strip", z.ZodTypeAny, {
|
|
36
|
+
password: string;
|
|
37
|
+
token: string;
|
|
38
|
+
}, {
|
|
39
|
+
password: string;
|
|
40
|
+
token: string;
|
|
41
|
+
}>;
|
|
42
|
+
export declare const emailVerificationRequestSchema: z.ZodObject<{
|
|
43
|
+
email: z.ZodString;
|
|
44
|
+
}, "strip", z.ZodTypeAny, {
|
|
45
|
+
email: string;
|
|
46
|
+
}, {
|
|
47
|
+
email: string;
|
|
48
|
+
}>;
|
|
49
|
+
export declare const emailVerificationConfirmSchema: z.ZodObject<{
|
|
50
|
+
token: z.ZodString;
|
|
51
|
+
}, "strip", z.ZodTypeAny, {
|
|
52
|
+
token: string;
|
|
53
|
+
}, {
|
|
54
|
+
token: string;
|
|
55
|
+
}>;
|
|
25
56
|
export type RegisterInput = z.infer<typeof registerSchema>;
|
|
26
57
|
export type LoginInput = z.infer<typeof loginSchema>;
|
|
58
|
+
export type PasswordResetRequestInput = z.infer<typeof passwordResetRequestSchema>;
|
|
59
|
+
export type PasswordResetConfirmInput = z.infer<typeof passwordResetConfirmSchema>;
|
|
60
|
+
export type EmailVerificationRequestInput = z.infer<typeof emailVerificationRequestSchema>;
|
|
61
|
+
export type EmailVerificationConfirmInput = z.infer<typeof emailVerificationConfirmSchema>;
|
|
@@ -8,3 +8,16 @@ export const loginSchema = z.object({
|
|
|
8
8
|
email: z.string().email(),
|
|
9
9
|
password: z.string().min(8),
|
|
10
10
|
});
|
|
11
|
+
export const passwordResetRequestSchema = z.object({
|
|
12
|
+
email: z.string().email(),
|
|
13
|
+
});
|
|
14
|
+
export const passwordResetConfirmSchema = z.object({
|
|
15
|
+
token: z.string().min(16),
|
|
16
|
+
password: z.string().min(8),
|
|
17
|
+
});
|
|
18
|
+
export const emailVerificationRequestSchema = z.object({
|
|
19
|
+
email: z.string().email(),
|
|
20
|
+
});
|
|
21
|
+
export const emailVerificationConfirmSchema = z.object({
|
|
22
|
+
token: z.string().min(16),
|
|
23
|
+
});
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
import type { LoginInput, RegisterInput } from './auth.schemas.js';
|
|
2
|
-
import type { AuthResult, AuthServiceDeps, AuthTokens, AuthUser } from './auth.types.js';
|
|
2
|
+
import type { AuthResult, AuthServiceDeps, AuthTokens, AuthUser, EmailVerificationConfirmInput, EmailVerificationRequestInput, OAuthProviderProfile, PasswordResetConfirmInput, PasswordResetRequestInput } from './auth.types.js';
|
|
3
3
|
export declare function makeAuthService(deps: AuthServiceDeps): {
|
|
4
4
|
registerUser(params: RegisterInput): Promise<AuthResult>;
|
|
5
5
|
loginUser(params: LoginInput): Promise<AuthResult>;
|
|
6
6
|
refreshSession(refreshToken: string): Promise<AuthTokens>;
|
|
7
|
+
revokeRefreshToken(refreshToken: string): Promise<{
|
|
8
|
+
ok: true;
|
|
9
|
+
}>;
|
|
7
10
|
getMe(userId: number | string): Promise<AuthUser | null>;
|
|
11
|
+
requestPasswordReset: (params: PasswordResetRequestInput) => Promise<{
|
|
12
|
+
ok: true;
|
|
13
|
+
}>;
|
|
14
|
+
confirmPasswordReset: (params: PasswordResetConfirmInput) => Promise<{
|
|
15
|
+
ok: true;
|
|
16
|
+
}>;
|
|
17
|
+
requestEmailVerification: (params: EmailVerificationRequestInput) => Promise<{
|
|
18
|
+
ok: true;
|
|
19
|
+
}>;
|
|
20
|
+
confirmEmailVerification: (params: EmailVerificationConfirmInput) => Promise<{
|
|
21
|
+
ok: true;
|
|
22
|
+
user: AuthUser;
|
|
23
|
+
}>;
|
|
24
|
+
signInWithOAuthProfile: (profile: OAuthProviderProfile) => Promise<AuthResult>;
|
|
25
|
+
loginWithOAuthProfile: (profile: OAuthProviderProfile) => Promise<AuthResult>;
|
|
8
26
|
};
|