exp-node-server 1.1.6 → 2.0.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 +82 -20
- package/dist/cli.js +525 -0
- package/package.json +29 -9
- package/templates/mongoose/.dockerignore +10 -0
- package/templates/mongoose/.env.example +56 -7
- package/templates/mongoose/.prettierignore +3 -0
- package/templates/mongoose/.prettierrc +7 -0
- package/templates/mongoose/Dockerfile +33 -0
- package/templates/mongoose/_gitignore +32 -0
- package/templates/mongoose/docker-compose.yml +21 -0
- package/templates/mongoose/eslint.config.js +35 -0
- package/templates/mongoose/package-lock.json +6377 -0
- package/templates/mongoose/package.json +49 -10
- package/templates/mongoose/readme.md +85 -0
- package/templates/mongoose/src/app/configs/index.ts +178 -26
- package/templates/mongoose/src/app/errors/zodError.ts +16 -16
- package/templates/mongoose/src/app/lib/db.ts +18 -0
- package/templates/mongoose/src/app/lib/logger.ts +12 -0
- package/templates/mongoose/src/app/middlewares/auth.ts +48 -44
- package/templates/mongoose/src/app/middlewares/global_error_handler.ts +59 -65
- package/templates/mongoose/src/app/middlewares/not_found_api.ts +7 -7
- package/templates/mongoose/src/app/middlewares/rate_limiter.ts +153 -0
- package/templates/mongoose/src/app/middlewares/request_validator.ts +11 -10
- package/templates/mongoose/src/app/middlewares/uploader.ts +17 -13
- package/templates/mongoose/src/app/modules/account/account.controller.ts +156 -0
- package/templates/mongoose/src/app/modules/account/account.interface.ts +65 -0
- package/templates/mongoose/src/app/modules/account/account.route.ts +101 -0
- package/templates/mongoose/src/app/modules/account/account.schema.ts +97 -0
- package/templates/mongoose/src/app/modules/account/account.service.ts +422 -0
- package/templates/mongoose/src/app/modules/account/account.swagger.ts +278 -0
- package/templates/mongoose/src/app/modules/account/account.validation.ts +63 -0
- package/templates/mongoose/src/app/modules/profile/profile.controller.ts +17 -0
- package/templates/mongoose/src/app/modules/profile/profile.interface.ts +7 -0
- package/templates/mongoose/src/app/modules/profile/profile.route.ts +28 -0
- package/templates/mongoose/src/app/modules/profile/profile.schema.ts +13 -0
- package/templates/mongoose/src/app/modules/profile/profile.service.ts +28 -0
- package/templates/mongoose/src/app/modules/profile/profile.swagger.ts +34 -0
- package/templates/mongoose/src/app/modules/profile/profile.validation.ts +8 -0
- package/templates/mongoose/src/app/passport-js/index.ts +87 -0
- package/templates/mongoose/src/app/passport-js/oauth-state.ts +150 -0
- package/templates/mongoose/src/app/passport-js/providers.ts +258 -0
- package/templates/mongoose/src/app/passport-js/social-account.ts +141 -0
- package/templates/mongoose/src/app/queues/agenda.ts +24 -0
- package/templates/mongoose/src/app/queues/email.queue.ts +53 -0
- package/templates/mongoose/src/app/queues/index.ts +13 -0
- package/templates/mongoose/src/app/templates/otpEmailTemplate.ts +185 -0
- package/templates/mongoose/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
- package/templates/mongoose/src/app/types/error.d.ts +7 -7
- package/templates/mongoose/src/app/types/express.d.ts +11 -0
- package/templates/mongoose/src/app/types/passport-oauth1.d.ts +51 -0
- package/templates/mongoose/src/app/utils/JWT.ts +38 -18
- package/templates/mongoose/src/app/utils/app_error.ts +10 -10
- package/templates/mongoose/src/app/utils/auth_cookie.ts +27 -0
- package/templates/mongoose/src/app/utils/catch_async.ts +8 -8
- package/templates/mongoose/src/app/utils/cloudinary.ts +26 -59
- package/templates/mongoose/src/app/utils/mail_sender.ts +26 -92
- package/templates/mongoose/src/app/utils/manage_response.ts +19 -19
- package/templates/mongoose/src/app/utils/otp.ts +20 -0
- package/templates/mongoose/src/app/utils/session.ts +55 -0
- package/templates/mongoose/src/app.ts +73 -42
- package/templates/mongoose/src/routes.ts +13 -13
- package/templates/mongoose/src/server.ts +38 -18
- package/templates/mongoose/src/swaggerOptions.ts +23 -23
- package/templates/mongoose/tests/app.test.ts +97 -0
- package/templates/mongoose/tests/global-setup.ts +15 -0
- package/templates/mongoose/tests/helpers/fixtures.ts +42 -0
- package/templates/mongoose/tests/routes/account.test.ts +614 -0
- package/templates/mongoose/tests/routes/profile.test.ts +95 -0
- package/templates/mongoose/tests/routes/social.test.ts +310 -0
- package/templates/mongoose/tests/setup.ts +25 -0
- package/templates/mongoose/tsconfig.json +16 -20
- package/templates/mongoose/vitest.config.ts +43 -0
- package/templates/prisma/.dockerignore +10 -0
- package/templates/prisma/.env.example +62 -6
- package/templates/prisma/.prettierignore +3 -0
- package/templates/prisma/.prettierrc +7 -0
- package/templates/prisma/.projectrc.json +1 -1
- package/templates/prisma/Dockerfile +21 -7
- package/templates/prisma/_gitignore +35 -0
- package/templates/prisma/docker-compose.yml +8 -7
- package/templates/prisma/eslint.config.js +35 -0
- package/templates/prisma/package-lock.json +7327 -0
- package/templates/prisma/package.json +47 -9
- package/templates/prisma/prisma/schema/account.schema.prisma +92 -10
- package/templates/prisma/prisma/schema/profile.schema.prisma +8 -3
- package/templates/prisma/prisma/schema/schema.prisma +1 -2
- package/templates/prisma/prisma.config.ts +5 -5
- package/templates/prisma/readme.md +81 -0
- package/templates/prisma/src/app/configs/index.ts +175 -26
- package/templates/prisma/src/app/errors/zodError.ts +16 -16
- package/templates/prisma/src/app/lib/logger.ts +12 -0
- package/templates/prisma/src/app/lib/prisma.ts +4 -6
- package/templates/prisma/src/app/middlewares/auth.ts +37 -16
- package/templates/prisma/src/app/middlewares/global_error_handler.ts +42 -35
- package/templates/prisma/src/app/middlewares/not_found_api.ts +7 -7
- package/templates/prisma/src/app/middlewares/rate_limiter.ts +124 -0
- package/templates/prisma/src/app/middlewares/request_validator.ts +4 -3
- package/templates/prisma/src/app/middlewares/uploader.ts +17 -13
- package/templates/prisma/src/app/modules/account/account.controller.ts +74 -34
- package/templates/prisma/src/app/modules/account/account.route.ts +68 -22
- package/templates/prisma/src/app/modules/account/account.service.ts +329 -309
- package/templates/prisma/src/app/modules/account/account.swagger.ts +167 -76
- package/templates/prisma/src/app/modules/account/account.validation.ts +41 -19
- package/templates/prisma/src/app/modules/profile/profile.controller.ts +4 -4
- package/templates/prisma/src/app/modules/profile/profile.route.ts +18 -12
- package/templates/prisma/src/app/modules/profile/profile.service.ts +11 -18
- package/templates/prisma/src/app/modules/profile/profile.swagger.ts +11 -11
- package/templates/prisma/src/app/modules/profile/profile.validation.ts +2 -2
- package/templates/prisma/src/app/passport-js/index.ts +87 -0
- package/templates/prisma/src/app/passport-js/oauth-state.ts +150 -0
- package/templates/prisma/src/app/passport-js/providers.ts +263 -0
- package/templates/prisma/src/app/passport-js/social-account.ts +147 -0
- package/templates/prisma/src/app/queues/boss.ts +8 -0
- package/templates/prisma/src/app/queues/email.queue.ts +56 -0
- package/templates/prisma/src/app/queues/index.ts +14 -0
- package/templates/prisma/src/app/queues/maintenance.queue.ts +30 -0
- package/templates/prisma/src/app/templates/otpEmailTemplate.ts +185 -0
- package/templates/prisma/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
- package/templates/prisma/src/app/types/error.d.ts +7 -7
- package/templates/prisma/src/app/types/express.d.ts +11 -0
- package/templates/prisma/src/app/types/passport-oauth1.d.ts +51 -0
- package/templates/prisma/src/app/utils/JWT.ts +36 -17
- package/templates/prisma/src/app/utils/app_error.ts +10 -10
- package/templates/prisma/src/app/utils/auth_cookie.ts +27 -0
- package/templates/prisma/src/app/utils/catch_async.ts +8 -8
- package/templates/prisma/src/app/utils/cloudinary.ts +26 -59
- package/templates/prisma/src/app/utils/mail_sender.ts +26 -92
- package/templates/prisma/src/app/utils/manage_response.ts +19 -19
- package/templates/prisma/src/app/utils/otp.ts +20 -0
- package/templates/prisma/src/app/utils/session.ts +57 -0
- package/templates/prisma/src/app.ts +73 -42
- package/templates/prisma/src/routes.ts +12 -10
- package/templates/prisma/src/server.ts +37 -19
- package/templates/prisma/src/swaggerOptions.ts +22 -26
- package/templates/prisma/tests/app.test.ts +86 -0
- package/templates/prisma/tests/helpers/fixtures.ts +35 -0
- package/templates/prisma/tests/helpers/prisma.ts +32 -0
- package/templates/prisma/tests/routes/account.test.ts +627 -0
- package/templates/prisma/tests/routes/profile.test.ts +91 -0
- package/templates/prisma/tests/routes/social.test.ts +317 -0
- package/templates/prisma/tests/setup.ts +10 -0
- package/templates/prisma/tsconfig.json +16 -23
- package/templates/prisma/vitest.config.ts +38 -0
- package/bin/cli.js +0 -41
- package/src/createProject.js +0 -61
- package/src/generateModule.js +0 -93
- package/src/utils/getProjectType.js +0 -13
- package/src/utils/mongoose/controllerGenerator.js +0 -70
- package/src/utils/mongoose/routeGenerator.js +0 -27
- package/src/utils/mongoose/schemaGenerator.js +0 -11
- package/src/utils/mongoose/serviceGenerator.js +0 -49
- package/src/utils/mongoose/swaggerGenerator.js +0 -112
- package/src/utils/mongoose/validationGenerator.js +0 -13
- package/src/utils/prisma/prismaSchemaGenerator.js +0 -10
- package/src/utils/prisma/prismaServiceGenerator.js +0 -47
- package/src/utils/routeInjector.js +0 -40
- package/src/utils/swaggerInjector.js +0 -52
- package/src/utils/validateModuleName.js +0 -17
- package/src/utils/validateProjectName.js +0 -15
- package/src/utils.js +0 -26
- package/templates/mongoose/src/app/errors/castError.ts +0 -23
- package/templates/mongoose/src/app/errors/duplicateError.ts +0 -26
- package/templates/mongoose/src/app/errors/validationError.ts +0 -25
- package/templates/mongoose/src/app/modules/auth/auth.controller.ts +0 -127
- package/templates/mongoose/src/app/modules/auth/auth.interface.ts +0 -24
- package/templates/mongoose/src/app/modules/auth/auth.route.ts +0 -46
- package/templates/mongoose/src/app/modules/auth/auth.schema.ts +0 -19
- package/templates/mongoose/src/app/modules/auth/auth.service.ts +0 -324
- package/templates/mongoose/src/app/modules/auth/auth.swagger.ts +0 -214
- package/templates/mongoose/src/app/modules/auth/auth.validation.ts +0 -37
- package/templates/mongoose/src/app/modules/user/user.controller.ts +0 -17
- package/templates/mongoose/src/app/modules/user/user.interface.ts +0 -15
- package/templates/mongoose/src/app/modules/user/user.route.ts +0 -20
- package/templates/mongoose/src/app/modules/user/user.schema.ts +0 -22
- package/templates/mongoose/src/app/modules/user/user.service.ts +0 -22
- package/templates/mongoose/src/app/modules/user/user.swagger.ts +0 -75
- package/templates/mongoose/src/app/modules/user/user.validation.ts +0 -18
- package/templates/mongoose/src/app/types/index.d.ts +0 -12
- package/templates/mongoose/src/app/utils/isAccountExist.ts +0 -31
- package/templates/prisma/dockerignore +0 -0
- package/templates/prisma/src/app/types/index.d.ts +0 -12
- package/templates/prisma/src/app/utils/otpGenerator.ts +0 -29
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import request from 'supertest';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import app from '../src/app.js';
|
|
4
|
+
import { makeAccount } from './helpers/fixtures.js';
|
|
5
|
+
import { prismaMock } from './helpers/prisma.js';
|
|
6
|
+
|
|
7
|
+
describe('app', () => {
|
|
8
|
+
it('GET / responds with security headers', async () => {
|
|
9
|
+
const res = await request(app).get('/');
|
|
10
|
+
|
|
11
|
+
expect(res.status).toBe(200);
|
|
12
|
+
expect(res.headers['x-powered-by']).toBeUndefined();
|
|
13
|
+
expect(res.headers['x-content-type-options']).toBe('nosniff');
|
|
14
|
+
expect(res.headers['content-security-policy']).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('unknown routes are 404', async () => {
|
|
18
|
+
const res = await request(app).get('/api/nope');
|
|
19
|
+
expect(res.status).toBe(404);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('malformed JSON is a 400, not a 500', async () => {
|
|
23
|
+
const res = await request(app)
|
|
24
|
+
.post('/api/auth/sign-in')
|
|
25
|
+
.set('Content-Type', 'application/json')
|
|
26
|
+
.send('{"email":');
|
|
27
|
+
|
|
28
|
+
expect(res.status).toBe(400);
|
|
29
|
+
expect(res.body.message).toBe('Invalid JSON body');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('rejects bodies over 1mb', async () => {
|
|
33
|
+
const res = await request(app)
|
|
34
|
+
.post('/api/auth/sign-in')
|
|
35
|
+
.send({ email: 'a@b.c', password: 'x'.repeat(1_100_000) });
|
|
36
|
+
|
|
37
|
+
expect(res.status).toBe(413);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('only allows the configured CORS origin', async () => {
|
|
41
|
+
const allowed = await request(app).get('/').set('Origin', 'http://localhost:5173');
|
|
42
|
+
const other = await request(app).get('/').set('Origin', 'https://evil.example');
|
|
43
|
+
|
|
44
|
+
expect(allowed.headers['access-control-allow-origin']).toBe('http://localhost:5173');
|
|
45
|
+
expect(other.headers['access-control-allow-origin']).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('rate limiting', () => {
|
|
50
|
+
it('limits sign-in attempts per email', async () => {
|
|
51
|
+
prismaMock.account.findUnique.mockResolvedValue(makeAccount());
|
|
52
|
+
const attempt = () =>
|
|
53
|
+
request(app)
|
|
54
|
+
.post('/api/auth/sign-in')
|
|
55
|
+
.send({ email: 'target@example.com', password: 'guess-guess' });
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < 10; i++) {
|
|
58
|
+
expect((await attempt()).status).toBe(401);
|
|
59
|
+
}
|
|
60
|
+
const blocked = await attempt();
|
|
61
|
+
|
|
62
|
+
expect(blocked.status).toBe(429);
|
|
63
|
+
expect(blocked.headers['ratelimit-policy']).toBeDefined();
|
|
64
|
+
|
|
65
|
+
// another account from the same client is still allowed
|
|
66
|
+
const other = await request(app)
|
|
67
|
+
.post('/api/auth/sign-in')
|
|
68
|
+
.send({ email: 'someone-else@example.com', password: 'guess-guess' });
|
|
69
|
+
expect(other.status).toBe(401);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('limits emails sent to one address, whatever the endpoint', async () => {
|
|
73
|
+
prismaMock.account.findUnique.mockResolvedValue(null);
|
|
74
|
+
const email = 'victim@example.com';
|
|
75
|
+
|
|
76
|
+
await request(app).put('/api/auth/forget-password').send({ email }).expect(200);
|
|
77
|
+
await request(app).put('/api/auth/resend-otp').send({ email }).expect(200);
|
|
78
|
+
await request(app)
|
|
79
|
+
.put('/api/auth/forget-password')
|
|
80
|
+
.send({ email: email.toUpperCase() })
|
|
81
|
+
.expect(200);
|
|
82
|
+
|
|
83
|
+
const blocked = await request(app).put('/api/auth/forget-password').send({ email });
|
|
84
|
+
expect(blocked.status).toBe(429);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Account } from '@prisma/client';
|
|
2
|
+
import bcrypt from 'bcrypt';
|
|
3
|
+
import { configs } from '../../src/app/configs/index.js';
|
|
4
|
+
import { jwtHelpers } from '../../src/app/utils/JWT.js';
|
|
5
|
+
|
|
6
|
+
export const PASSWORD = 'correct-horse-battery';
|
|
7
|
+
const passwordHash = bcrypt.hashSync(PASSWORD, 4);
|
|
8
|
+
|
|
9
|
+
/** A verified, active account with a password. */
|
|
10
|
+
export const makeAccount = (overrides: Partial<Account> = {}): Account => ({
|
|
11
|
+
id: 'acc-1',
|
|
12
|
+
email: 'user@example.com',
|
|
13
|
+
password: passwordHash,
|
|
14
|
+
provider: 'LOCAL',
|
|
15
|
+
role: 'USER',
|
|
16
|
+
isDeleted: false,
|
|
17
|
+
isAccountVerified: true,
|
|
18
|
+
tokenVersion: 0,
|
|
19
|
+
lastOtp: null,
|
|
20
|
+
lastOtpSendingTime: null,
|
|
21
|
+
otpAttempts: 0,
|
|
22
|
+
createdAt: new Date(),
|
|
23
|
+
updatedAt: new Date(),
|
|
24
|
+
...overrides,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/** A signed access token, as the app would issue it for `account`. */
|
|
28
|
+
export const accessTokenFor = (account: Account) =>
|
|
29
|
+
jwtHelpers.generateToken(
|
|
30
|
+
{ accountId: account.id, email: account.email, role: account.role, tv: account.tokenVersion },
|
|
31
|
+
configs.jwt.access_token,
|
|
32
|
+
'1h',
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const bearer = (account: Account) => `Bearer ${accessTokenFor(account)}`;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Mock, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const model = () => ({
|
|
4
|
+
findUnique: vi.fn(),
|
|
5
|
+
findFirst: vi.fn(),
|
|
6
|
+
findMany: vi.fn(),
|
|
7
|
+
count: vi.fn(),
|
|
8
|
+
create: vi.fn(),
|
|
9
|
+
update: vi.fn(),
|
|
10
|
+
updateMany: vi.fn(),
|
|
11
|
+
upsert: vi.fn(),
|
|
12
|
+
delete: vi.fn(),
|
|
13
|
+
deleteMany: vi.fn(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
type ModelMock = ReturnType<typeof model>;
|
|
17
|
+
|
|
18
|
+
const models = new Map<string, ModelMock>();
|
|
19
|
+
const client = { $queryRaw: vi.fn(), $transaction: vi.fn() };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Stands in for the Prisma client (see tests/setup.ts), so tests need no database.
|
|
23
|
+
* Any model works without registering it: `prismaMock.order.findMany` is created
|
|
24
|
+
* on first use.
|
|
25
|
+
*/
|
|
26
|
+
export const prismaMock = new Proxy(client, {
|
|
27
|
+
get(target, prop: string) {
|
|
28
|
+
if (prop in target) return target[prop as keyof typeof target];
|
|
29
|
+
if (!models.has(prop)) models.set(prop, model());
|
|
30
|
+
return models.get(prop);
|
|
31
|
+
},
|
|
32
|
+
}) as typeof client & Record<string, ModelMock> & { $queryRaw: Mock };
|