nicot-simple-user 1.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.
Files changed (98) hide show
  1. package/.prettierrc +4 -0
  2. package/Dockerfile.puppeteer +45 -0
  3. package/LICENSE +22 -0
  4. package/README.md +414 -0
  5. package/config.example.yaml +7 -0
  6. package/dist/app.module.d.ts +2 -0
  7. package/dist/app.module.js +53 -0
  8. package/dist/app.module.js.map +1 -0
  9. package/dist/main.d.ts +1 -0
  10. package/dist/main.js +26 -0
  11. package/dist/main.js.map +1 -0
  12. package/dist/simple-user/aragami-init.d.ts +2 -0
  13. package/dist/simple-user/aragami-init.js +55 -0
  14. package/dist/simple-user/aragami-init.js.map +1 -0
  15. package/dist/simple-user/index.d.ts +6 -0
  16. package/dist/simple-user/index.js +23 -0
  17. package/dist/simple-user/index.js.map +1 -0
  18. package/dist/simple-user/login/login.controller.d.ts +14 -0
  19. package/dist/simple-user/login/login.controller.js +90 -0
  20. package/dist/simple-user/login/login.controller.js.map +1 -0
  21. package/dist/simple-user/module-builder.d.ts +2 -0
  22. package/dist/simple-user/module-builder.js +34 -0
  23. package/dist/simple-user/module-builder.js.map +1 -0
  24. package/dist/simple-user/options.d.ts +26 -0
  25. package/dist/simple-user/options.js +3 -0
  26. package/dist/simple-user/options.js.map +1 -0
  27. package/dist/simple-user/resolver.d.ts +41 -0
  28. package/dist/simple-user/resolver.js +48 -0
  29. package/dist/simple-user/resolver.js.map +1 -0
  30. package/dist/simple-user/send-code/code-context.d.ts +4 -0
  31. package/dist/simple-user/send-code/code-context.js +8 -0
  32. package/dist/simple-user/send-code/code-context.js.map +1 -0
  33. package/dist/simple-user/send-code/decorators.d.ts +2 -0
  34. package/dist/simple-user/send-code/decorators.js +13 -0
  35. package/dist/simple-user/send-code/decorators.js.map +1 -0
  36. package/dist/simple-user/send-code/send-code.controller.d.ts +9 -0
  37. package/dist/simple-user/send-code/send-code.controller.js +71 -0
  38. package/dist/simple-user/send-code/send-code.controller.js.map +1 -0
  39. package/dist/simple-user/send-code/send-code.dto.d.ts +12 -0
  40. package/dist/simple-user/send-code/send-code.dto.js +55 -0
  41. package/dist/simple-user/send-code/send-code.dto.js.map +1 -0
  42. package/dist/simple-user/send-code/send-code.service.d.ts +18 -0
  43. package/dist/simple-user/send-code/send-code.service.js +144 -0
  44. package/dist/simple-user/send-code/send-code.service.js.map +1 -0
  45. package/dist/simple-user/send-code/wait-time.dto.d.ts +3 -0
  46. package/dist/simple-user/send-code/wait-time.dto.js +29 -0
  47. package/dist/simple-user/send-code/wait-time.dto.js.map +1 -0
  48. package/dist/simple-user/simple-user/change-email.dto.d.ts +3 -0
  49. package/dist/simple-user/simple-user/change-email.dto.js +12 -0
  50. package/dist/simple-user/simple-user/change-email.dto.js.map +1 -0
  51. package/dist/simple-user/simple-user/change-password.dto.d.ts +4 -0
  52. package/dist/simple-user/simple-user/change-password.dto.js +41 -0
  53. package/dist/simple-user/simple-user/change-password.dto.js.map +1 -0
  54. package/dist/simple-user/simple-user/email.dto.d.ts +6 -0
  55. package/dist/simple-user/simple-user/email.dto.js +46 -0
  56. package/dist/simple-user/simple-user/email.dto.js.map +1 -0
  57. package/dist/simple-user/simple-user/login.dto.d.ts +11 -0
  58. package/dist/simple-user/simple-user/login.dto.js +80 -0
  59. package/dist/simple-user/simple-user/login.dto.js.map +1 -0
  60. package/dist/simple-user/simple-user/reset-password.dto.d.ts +4 -0
  61. package/dist/simple-user/simple-user/reset-password.dto.js +32 -0
  62. package/dist/simple-user/simple-user/reset-password.dto.js.map +1 -0
  63. package/dist/simple-user/simple-user/simple-user.service.d.ts +33 -0
  64. package/dist/simple-user/simple-user/simple-user.service.js +338 -0
  65. package/dist/simple-user/simple-user/simple-user.service.js.map +1 -0
  66. package/dist/simple-user/simple-user/user-exists.dto.d.ts +3 -0
  67. package/dist/simple-user/simple-user/user-exists.dto.js +28 -0
  68. package/dist/simple-user/simple-user/user-exists.dto.js.map +1 -0
  69. package/dist/simple-user/simple-user.entity.d.ts +40 -0
  70. package/dist/simple-user/simple-user.entity.js +119 -0
  71. package/dist/simple-user/simple-user.entity.js.map +1 -0
  72. package/dist/simple-user/simple-user.module.d.ts +8 -0
  73. package/dist/simple-user/simple-user.module.js +52 -0
  74. package/dist/simple-user/simple-user.module.js.map +1 -0
  75. package/dist/simple-user/tokens.d.ts +2 -0
  76. package/dist/simple-user/tokens.js +6 -0
  77. package/dist/simple-user/tokens.js.map +1 -0
  78. package/dist/simple-user/user-center/patch-me.d.ts +2 -0
  79. package/dist/simple-user/user-center/patch-me.js +16 -0
  80. package/dist/simple-user/user-center/patch-me.js.map +1 -0
  81. package/dist/simple-user/user-center/user-center.controller.d.ts +13 -0
  82. package/dist/simple-user/user-center/user-center.controller.js +88 -0
  83. package/dist/simple-user/user-center/user-center.controller.js.map +1 -0
  84. package/dist/simple-user/user-center/user-center.service.d.ts +2 -0
  85. package/dist/simple-user/user-center/user-center.service.js +17 -0
  86. package/dist/simple-user/user-center/user-center.service.js.map +1 -0
  87. package/dist/tsconfig.build.tsbuildinfo +1 -0
  88. package/dist/utility/load-config.d.ts +7 -0
  89. package/dist/utility/load-config.js +61 -0
  90. package/dist/utility/load-config.js.map +1 -0
  91. package/eslint.config.mjs +34 -0
  92. package/install-npm-typeorm.sh +3 -0
  93. package/nest-cli.json +7 -0
  94. package/package.json +98 -0
  95. package/test/app-e2e.spec.ts +242 -0
  96. package/test/jest-e2e.json +9 -0
  97. package/tsconfig.build.json +4 -0
  98. package/tsconfig.json +18 -0
@@ -0,0 +1,7 @@
1
+ declare const defaultConfig: {
2
+ host: string;
3
+ port: number;
4
+ };
5
+ export type LoadConfig = typeof defaultConfig;
6
+ export declare function loadConfig(): Promise<LoadConfig>;
7
+ export {};
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.loadConfig = loadConfig;
40
+ const yaml_1 = __importDefault(require("yaml"));
41
+ const fs = __importStar(require("fs"));
42
+ const defaultConfig = {
43
+ host: '::',
44
+ port: 3000,
45
+ };
46
+ async function loadConfig() {
47
+ let readConfig = {};
48
+ try {
49
+ const configText = await fs.promises.readFile('./config.yaml', 'utf-8');
50
+ readConfig = yaml_1.default.parse(configText);
51
+ }
52
+ catch (e) {
53
+ console.error(`Failed to read config: ${e.toString()}`);
54
+ }
55
+ return {
56
+ ...defaultConfig,
57
+ ...readConfig,
58
+ ...process.env,
59
+ };
60
+ }
61
+ //# sourceMappingURL=load-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../src/utility/load-config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,gCAaC;AAvBD,gDAAwB;AACxB,uCAAyB;AAEzB,MAAM,aAAa,GAAG;IACpB,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;CACX,CAAC;AAIK,KAAK,UAAU,UAAU;IAC9B,IAAI,UAAU,GAAwB,EAAE,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QACxE,UAAU,GAAG,cAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO;QACL,GAAG,aAAa;QAChB,GAAG,UAAU;QACb,GAAG,OAAO,CAAC,GAAG;KACf,CAAC;AACJ,CAAC"}
@@ -0,0 +1,34 @@
1
+ // @ts-check
2
+ import eslint from '@eslint/js';
3
+ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
4
+ import globals from 'globals';
5
+ import tseslint from 'typescript-eslint';
6
+
7
+ export default tseslint.config(
8
+ {
9
+ ignores: ['eslint.config.mjs'],
10
+ },
11
+ eslint.configs.recommended,
12
+ ...tseslint.configs.recommendedTypeChecked,
13
+ eslintPluginPrettierRecommended,
14
+ {
15
+ languageOptions: {
16
+ globals: {
17
+ ...globals.node,
18
+ ...globals.jest,
19
+ },
20
+ sourceType: 'commonjs',
21
+ parserOptions: {
22
+ projectService: true,
23
+ tsconfigRootDir: import.meta.dirname,
24
+ },
25
+ },
26
+ },
27
+ {
28
+ rules: {
29
+ '@typescript-eslint/no-explicit-any': 'off',
30
+ '@typescript-eslint/no-floating-promises': 'warn',
31
+ '@typescript-eslint/no-unsafe-argument': 'warn'
32
+ },
33
+ },
34
+ );
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ npm install --save typeorm @nestjs/typeorm pg nicot
package/nest-cli.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "collection": "@nestjs/schematics",
3
+ "sourceRoot": "src",
4
+ "compilerOptions": {
5
+ "plugins": ["@nestjs/swagger"]
6
+ }
7
+ }
package/package.json ADDED
@@ -0,0 +1,98 @@
1
+ {
2
+ "name": "nicot-simple-user",
3
+ "version": "1.0.0",
4
+ "description": "Simple user module based on NICOT",
5
+ "author": "Nanahira <nanahira@momobako.com>",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/purerosefallen/nicot-simple-user.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/purerosefallen/nicot-simple-user/issues"
13
+ },
14
+ "homepage": "https://github.com/purerosefallen/nicot-simple-user",
15
+ "main": "dist/simple-user/index.js",
16
+ "types": "dist/simple-user/index.d.ts",
17
+ "scripts": {
18
+ "build": "nest build",
19
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
20
+ "start": "nest start",
21
+ "start:dev": "nest start --watch",
22
+ "start:debug": "nest start --debug --watch",
23
+ "start:prod": "node dist/main",
24
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
25
+ "test": "jest",
26
+ "test:watch": "jest --watch",
27
+ "test:cov": "jest --coverage",
28
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
29
+ "test:e2e": "jest --config ./test/jest-e2e.json"
30
+ },
31
+ "dependencies": {
32
+ "aragami": "^1.2.9",
33
+ "argon2": "^0.44.0",
34
+ "crypto-random-string": "3.3.1",
35
+ "nestjs-aragami": "^1.2.0"
36
+ },
37
+ "peerDependencies": {
38
+ "@nestjs/common": "^11.0.1",
39
+ "@nestjs/core": "^11.0.1",
40
+ "@nestjs/swagger": "^11.2.3",
41
+ "@nestjs/typeorm": "^11.0.0",
42
+ "class-transformer": "^0.5.1",
43
+ "class-validator": "^0.14.3",
44
+ "nicot": "^1.3.1",
45
+ "typeorm": "^0.3.28"
46
+ },
47
+ "devDependencies": {
48
+ "@eslint/eslintrc": "^3.2.0",
49
+ "@eslint/js": "^9.18.0",
50
+ "@nestjs/cli": "^11.0.0",
51
+ "@nestjs/schematics": "^11.0.0",
52
+ "@nestjs/testing": "^11.0.1",
53
+ "@swc/cli": "^0.6.0",
54
+ "@swc/core": "^1.10.7",
55
+ "@types/express": "^5.0.6",
56
+ "@types/jest": "^29.5.14",
57
+ "@types/node": "^22.10.7",
58
+ "@types/supertest": "^6.0.2",
59
+ "eslint": "^9.18.0",
60
+ "eslint-config-prettier": "^10.0.1",
61
+ "eslint-plugin-prettier": "^5.2.2",
62
+ "globals": "^16.0.0",
63
+ "jest": "^29.7.0",
64
+ "prettier": "^3.4.2",
65
+ "source-map-support": "^0.5.21",
66
+ "supertest": "^7.0.0",
67
+ "ts-jest": "^29.2.5",
68
+ "ts-loader": "^9.5.2",
69
+ "ts-node": "^10.9.2",
70
+ "tsconfig-paths": "^4.2.0",
71
+ "typescript": "^5.7.3",
72
+ "typescript-eslint": "^8.20.0",
73
+ "@nestjs/platform-express": "^11.0.1",
74
+ "@nestjs/config": "^4.0.2",
75
+ "nesties": "^1.1.26",
76
+ "pg": "^8.16.3",
77
+ "reflect-metadata": "^0.2.2",
78
+ "rxjs": "^7.8.1",
79
+ "yaml": "^2.8.2"
80
+ },
81
+ "jest": {
82
+ "moduleFileExtensions": [
83
+ "js",
84
+ "json",
85
+ "ts"
86
+ ],
87
+ "rootDir": "test",
88
+ "testRegex": ".*\\.spec\\.ts$",
89
+ "transform": {
90
+ "^.+\\.(t|j)s$": "ts-jest"
91
+ },
92
+ "collectCoverageFrom": [
93
+ "**/*.(t|j)s"
94
+ ],
95
+ "coverageDirectory": "../coverage",
96
+ "testEnvironment": "node"
97
+ }
98
+ }
@@ -0,0 +1,242 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { INestApplication } from '@nestjs/common';
3
+ import request from 'supertest';
4
+ import { AppModule } from './../src/app.module';
5
+
6
+ type BlankReturnMessageDto = {
7
+ statusCode: number;
8
+ message: string;
9
+ success: boolean;
10
+ timestamp: string;
11
+ };
12
+
13
+ type ReturnMessage<T> = BlankReturnMessageDto & { data?: T };
14
+
15
+ type LoginResponseDto = {
16
+ token: string;
17
+ tokenExpiresAt: string;
18
+ userId: number;
19
+ };
20
+
21
+ type SimpleUserResultDto = {
22
+ id: number;
23
+ email?: string;
24
+ passwordSet: boolean;
25
+ };
26
+
27
+ describe('SimpleUserModule (e2e)', () => {
28
+ let app: INestApplication;
29
+ let httpServer: any;
30
+
31
+ // 固定:测试模式下验证码永远是 123456
32
+ const TEST_CODE = '123456';
33
+
34
+ // 每次跑用不同邮箱,避免被数据库里历史数据影响
35
+ const rand = Math.random().toString(16).slice(2);
36
+ const email1 = `e2e_${rand}@example.com`;
37
+ const email2 = `e2e2_${rand}@example.com`;
38
+
39
+ // x-client-ssaid 是你体系里“客户端会话标识”
40
+ const ssaid = `ssaid_e2e_${rand}`;
41
+
42
+ // 密码在流程中会变
43
+ const password1 = `P@ss_${rand}_1`;
44
+ const password2 = `P@ss_${rand}_2`;
45
+
46
+ let token: string | undefined;
47
+
48
+ beforeAll(async () => {
49
+ const moduleFixture: TestingModule = await Test.createTestingModule({
50
+ imports: [AppModule],
51
+ }).compile();
52
+
53
+ app = moduleFixture.createNestApplication();
54
+ await app.init();
55
+ httpServer = app.getHttpServer();
56
+ });
57
+
58
+ afterAll(async () => {
59
+ await app.close();
60
+ });
61
+
62
+ function expectOkEnvelope(resBody: any) {
63
+ // 你的所有返回都有这些字段(BlankReturnMessageDto)
64
+ expect(resBody).toHaveProperty('statusCode');
65
+ expect(resBody).toHaveProperty('message');
66
+ expect(resBody).toHaveProperty('success');
67
+ expect(resBody).toHaveProperty('timestamp');
68
+ }
69
+
70
+ it('GET /login/user-exists -> should be false for a brand new email', async () => {
71
+ const res = await request(httpServer)
72
+ .get('/login/user-exists')
73
+ .query({ email: email1 })
74
+ .expect(200);
75
+
76
+ expectOkEnvelope(res.body);
77
+ expect(res.body.data).toBeDefined();
78
+ expect(res.body.data.exists).toBe(false);
79
+ });
80
+
81
+ it('POST /send-code/send (login) -> should succeed', async () => {
82
+ const res = await request(httpServer)
83
+ .post('/send-code/send')
84
+ .set('x-client-ssaid', ssaid)
85
+ .send({ email: email1, codePurpose: 'Login' })
86
+ .expect(200);
87
+
88
+ expectOkEnvelope(res.body);
89
+ });
90
+
91
+ it('GET /send-code/verify (login) -> correct code should pass; wrong code should 403', async () => {
92
+ // 正确验证码
93
+ const ok = await request(httpServer)
94
+ .get('/send-code/verify')
95
+ .query({ email: email1, codePurpose: 'Login', code: TEST_CODE })
96
+ .expect(200);
97
+
98
+ expectOkEnvelope(ok.body);
99
+
100
+ // 错误验证码
101
+ const bad = await request(httpServer)
102
+ .get('/send-code/verify')
103
+ .query({ email: email1, codePurpose: 'Login', code: '000000' })
104
+ .expect(403);
105
+
106
+ expectOkEnvelope(bad.body);
107
+ });
108
+
109
+ it('POST /login (code) -> should auto-create user & return token', async () => {
110
+ const res = await request(httpServer)
111
+ .post('/login')
112
+ .set('x-client-ssaid', ssaid)
113
+ .send({ email: email1, code: TEST_CODE })
114
+ .expect(200);
115
+
116
+ expectOkEnvelope(res.body);
117
+ const data = (res.body as ReturnMessage<LoginResponseDto>).data;
118
+ expect(data).toBeDefined();
119
+ expect(typeof data.userId).toBe('number');
120
+ expect(typeof data.token).toBe('string');
121
+ expect(data.token).toHaveLength(64);
122
+
123
+ token = data.token;
124
+ });
125
+
126
+ it('GET /user-center/me -> should return current user info', async () => {
127
+ const res = await request(httpServer)
128
+ .get('/user-center/me')
129
+ .set('x-client-ssaid', ssaid)
130
+ .set('x-client-token', token)
131
+ .expect(200);
132
+
133
+ expectOkEnvelope(res.body);
134
+ const data = (res.body as ReturnMessage<SimpleUserResultDto>).data;
135
+ expect(data).toBeDefined();
136
+ expect(typeof data.id).toBe('number');
137
+ // 刚用 email 登录,通常应该有 email
138
+ expect(data.email).toBe(email1);
139
+ expect(typeof data.passwordSet).toBe('boolean');
140
+ });
141
+
142
+ it('POST /user-center/change-password -> set password (first time) and then login by password', async () => {
143
+ // 第一次设置密码:currentPassword 可能不需要(passwordSet=false 时)
144
+ const setRes = await request(httpServer)
145
+ .post('/user-center/change-password')
146
+ .set('x-client-ssaid', ssaid)
147
+ .set('x-client-token', token)
148
+ .send({ newPassword: password1 })
149
+ .expect(200);
150
+
151
+ expectOkEnvelope(setRes.body);
152
+
153
+ // 用密码登录成功
154
+ const loginOk = await request(httpServer)
155
+ .post('/login')
156
+ .set('x-client-ssaid', ssaid)
157
+ .send({ email: email1, password: password1 })
158
+ .expect(200);
159
+
160
+ expectOkEnvelope(loginOk.body);
161
+ const data = (loginOk.body as ReturnMessage<LoginResponseDto>).data;
162
+ expect(data.token).toHaveLength(64);
163
+
164
+ // 用错密码应 403
165
+ const loginBad = await request(httpServer)
166
+ .post('/login')
167
+ .set('x-client-ssaid', ssaid)
168
+ .send({ email: email1, password: 'WRONG_PASSWORD' })
169
+ .expect(403);
170
+
171
+ expectOkEnvelope(loginBad.body);
172
+ });
173
+
174
+ it('POST /send-code/send (ChangeEmail) -> POST /user-center/change-email -> should change email', async () => {
175
+ // 发给新邮箱的验证码(purpose 注意大小写:ChangeEmail)
176
+ const send = await request(httpServer)
177
+ .post('/send-code/send')
178
+ .set('x-client-ssaid', ssaid)
179
+ .send({ email: email2, codePurpose: 'ChangeEmail' })
180
+ .expect(200);
181
+
182
+ expectOkEnvelope(send.body);
183
+
184
+ // 提交换绑
185
+ const change = await request(httpServer)
186
+ .post('/user-center/change-email')
187
+ .set('x-client-ssaid', ssaid)
188
+ .set('x-client-token', token)
189
+ .send({ email: email2, code: TEST_CODE })
190
+ .expect(200);
191
+
192
+ expectOkEnvelope(change.body);
193
+
194
+ // 用“新邮箱 + 旧密码”再次登录验证换绑生效
195
+ const relogin = await request(httpServer)
196
+ .post('/login')
197
+ .set('x-client-ssaid', ssaid)
198
+ .send({ email: email2, password: password1 })
199
+ .expect(200);
200
+
201
+ expectOkEnvelope(relogin.body);
202
+ const data = (relogin.body as ReturnMessage<LoginResponseDto>).data;
203
+ expect(data.token).toHaveLength(64);
204
+ });
205
+
206
+ it('POST /send-code/send (ResetPassword) -> POST /login/reset-password -> login with new password', async () => {
207
+ // 发 reset code(purpose 注意大小写:ResetPassword)
208
+ const send = await request(httpServer)
209
+ .post('/send-code/send')
210
+ .set('x-client-ssaid', ssaid)
211
+ .send({ email: email2, codePurpose: 'ResetPassword' })
212
+ .expect(200);
213
+
214
+ expectOkEnvelope(send.body);
215
+
216
+ // 重置密码
217
+ const reset = await request(httpServer)
218
+ .post('/login/reset-password')
219
+ .send({ email: email2, code: TEST_CODE, newPassword: password2 })
220
+ .expect(200);
221
+
222
+ expectOkEnvelope(reset.body);
223
+
224
+ // 老密码应失败
225
+ const oldBad = await request(httpServer)
226
+ .post('/login')
227
+ .set('x-client-ssaid', ssaid)
228
+ .send({ email: email2, password: password1 })
229
+ .expect(403);
230
+
231
+ expectOkEnvelope(oldBad.body);
232
+
233
+ // 新密码应成功
234
+ const newOk = await request(httpServer)
235
+ .post('/login')
236
+ .set('x-client-ssaid', ssaid)
237
+ .send({ email: email2, password: password2 })
238
+ .expect(200);
239
+
240
+ expectOkEnvelope(newOk.body);
241
+ });
242
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "moduleFileExtensions": ["js", "json", "ts"],
3
+ "rootDir": ".",
4
+ "testEnvironment": "node",
5
+ "testRegex": ".e2e-spec.ts$",
6
+ "transform": {
7
+ "^.+\\.(t|j)s$": "ts-jest"
8
+ }
9
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "declaration": true,
5
+ "removeComments": true,
6
+ "emitDecoratorMetadata": true,
7
+ "experimentalDecorators": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "target": "es2021",
10
+ "sourceMap": true,
11
+ "outDir": "./dist",
12
+ "baseUrl": "./",
13
+ "incremental": true,
14
+ "esModuleInterop": true
15
+ },
16
+ "compileOnSave": true,
17
+ "allowJs": true
18
+ }