@sync-in/server 1.10.0 → 1.10.1
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 +9 -0
- package/README.md +2 -2
- package/package.json +2 -3
- package/server/app.bootstrap.js +3 -22
- package/server/app.bootstrap.js.map +1 -1
- package/server/applications/comments/services/comments-queries.service.js +5 -9
- package/server/applications/comments/services/comments-queries.service.js.map +1 -1
- package/server/applications/users/users.e2e-spec.js +0 -1
- package/server/applications/users/users.e2e-spec.js.map +1 -1
- package/server/applications/webdav/constants/webdav.js +7 -0
- package/server/applications/webdav/constants/webdav.js.map +1 -1
- package/server/applications/webdav/utils/bootstrap.js +45 -0
- package/server/applications/webdav/utils/bootstrap.js.map +1 -0
- package/server/applications/webdav/webdav.controller.js +1 -1
- package/server/applications/webdav/webdav.controller.js.map +1 -1
- package/server/applications/webdav/webdav.e2e-spec.js +131 -2
- package/server/applications/webdav/webdav.e2e-spec.js.map +1 -1
- package/server/authentication/auth.e2e-spec.js +12 -6
- package/server/authentication/auth.e2e-spec.js.map +1 -1
- package/server/authentication/guards/auth-basic.guard.spec.js +23 -0
- package/server/authentication/guards/auth-basic.guard.spec.js.map +1 -1
- package/server/authentication/guards/auth-basic.strategy.js +3 -3
- package/server/authentication/guards/auth-basic.strategy.js.map +1 -1
- package/server/authentication/guards/auth-digest.strategy.js +32 -11
- package/server/authentication/guards/auth-digest.strategy.js.map +1 -1
- package/server/authentication/guards/implementations/http-basic.strategy.js +76 -0
- package/server/authentication/guards/implementations/http-basic.strategy.js.map +1 -0
- package/server/authentication/guards/implementations/http-digest.strategy.js +155 -0
- package/server/authentication/guards/implementations/http-digest.strategy.js.map +1 -0
- package/server/authentication/services/auth-manager.service.js +1 -2
- package/server/authentication/services/auth-manager.service.js.map +1 -1
- package/static/{chunk-XBKCQCBI.js → chunk-2GXOVGTD.js} +1 -1
- package/static/{chunk-QHJT5H4M.js → chunk-3MVPXC3U.js} +1 -1
- package/static/{chunk-I5SPA4G2.js → chunk-3VRUIWQG.js} +1 -1
- package/static/{chunk-L3BIP4AA.js → chunk-3ZBAQTHJ.js} +1 -1
- package/static/{chunk-D55YR5X7.js → chunk-76M3BMK6.js} +11 -11
- package/static/{chunk-GXWGB7WO.js → chunk-76REYAEA.js} +1 -1
- package/static/{chunk-CCZWPM7Q.js → chunk-7HJFIMNF.js} +1 -1
- package/static/{chunk-NIR4YE2E.js → chunk-7KAYOR3A.js} +1 -1
- package/static/{chunk-O3YLAEVE.js → chunk-AALPWGPB.js} +2 -2
- package/static/{chunk-KWFELZTM.js → chunk-CN5YVRFT.js} +1 -1
- package/static/{chunk-B6HQYQYG.js → chunk-CVXLHSO5.js} +1 -1
- package/static/{chunk-R7PLNX75.js → chunk-D2MLAO5N.js} +1 -1
- package/static/{chunk-HGODIZTV.js → chunk-EKWB5W72.js} +1 -1
- package/static/{chunk-PQZLR4P3.js → chunk-FTFEQDWH.js} +1 -1
- package/static/{chunk-3WZ6F3LC.js → chunk-FWQJ4ZCD.js} +1 -1
- package/static/{chunk-FIUF2JM4.js → chunk-IHS5LSJJ.js} +1 -1
- package/static/{chunk-G3PL6YX3.js → chunk-J7474P3L.js} +1 -1
- package/static/{chunk-LGIVVJDD.js → chunk-JAJ7VXMB.js} +1 -1
- package/static/{chunk-T42BV6TR.js → chunk-KEZNIIFH.js} +1 -1
- package/static/{chunk-OUHCDDT6.js → chunk-LWSCODLD.js} +1 -1
- package/static/{chunk-6WMXMIE4.js → chunk-NIKNG2FX.js} +1 -1
- package/static/{chunk-KPOQLDWF.js → chunk-QGHNJVJ6.js} +1 -1
- package/static/{chunk-GWRAGN3M.js → chunk-QJ22N76V.js} +1 -1
- package/static/{chunk-NJJURHX4.js → chunk-QTPIEEZW.js} +1 -1
- package/static/{chunk-ZHOE5VEY.js → chunk-R4VYKZVJ.js} +1 -1
- package/static/{chunk-7VRYTDX4.js → chunk-RBTLSPYJ.js} +1 -1
- package/static/{chunk-GQHXYX6Z.js → chunk-S44QIK3G.js} +1 -1
- package/static/{chunk-45AZ6ZML.js → chunk-S6H2ELRY.js} +1 -1
- package/static/{chunk-E32J777S.js → chunk-SPQH3ATC.js} +1 -1
- package/static/{chunk-DGCVA6BM.js → chunk-TTWMFWEC.js} +1 -1
- package/static/{chunk-ULSPQ3HP.js → chunk-U5E5H2DD.js} +1 -1
- package/static/{chunk-LNLBIJZD.js → chunk-VBTZDHZ3.js} +1 -1
- package/static/{chunk-I3FR3A45.js → chunk-VZFZUI6D.js} +1 -1
- package/static/{chunk-S3TTWPQA.js → chunk-WFMEUST4.js} +1 -1
- package/static/{chunk-27Z3SYRL.js → chunk-WRK2FTKU.js} +1 -1
- package/static/{chunk-4TPFERL6.js → chunk-WZPF4LS2.js} +1 -1
- package/static/{chunk-POUWUMC4.js → chunk-X7NHX5C7.js} +1 -1
- package/static/{chunk-XTVNHFKX.js → chunk-XSURUW7C.js} +1 -1
- package/static/{chunk-5O66CLTD.js → chunk-XX3JPJUM.js} +1 -1
- package/static/{chunk-3RPUQ22U.js → chunk-XZHWESIY.js} +1 -1
- package/static/{chunk-3JYMJQYT.js → chunk-ZHUBWKA2.js} +1 -1
- package/static/{chunk-XEWLBWFF.js → chunk-ZU5MQTFN.js} +1 -1
- package/static/index.html +1 -1
- package/static/{main-YKDNJ7LK.js → main-5O3KLGIR.js} +3 -3
|
@@ -7,7 +7,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
value: true
|
|
8
8
|
});
|
|
9
9
|
const _appbootstrap = require("../../app.bootstrap");
|
|
10
|
-
const _utils = require("../../infrastructure/database/utils");
|
|
11
10
|
const _webdav = require("./constants/webdav");
|
|
12
11
|
const XML_VERSION_STR = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>';
|
|
13
12
|
describe('WebDAV (e2e)', ()=>{
|
|
@@ -18,7 +17,6 @@ describe('WebDAV (e2e)', ()=>{
|
|
|
18
17
|
await app.getHttpAdapter().getInstance().ready();
|
|
19
18
|
});
|
|
20
19
|
afterAll(async ()=>{
|
|
21
|
-
await (0, _utils.dbCloseConnection)(app);
|
|
22
20
|
await app.close();
|
|
23
21
|
});
|
|
24
22
|
it('should be defined', ()=>{
|
|
@@ -64,6 +62,137 @@ describe('WebDAV (e2e)', ()=>{
|
|
|
64
62
|
});
|
|
65
63
|
expect(res.statusCode).toEqual(207);
|
|
66
64
|
});
|
|
65
|
+
describe('PUT with non-XML Content-Types (stream preservation)', ()=>{
|
|
66
|
+
const testFilePath = '/webdav/personal/test-content-type.txt';
|
|
67
|
+
const auth = 'Basic am86cGFzc3dvcmQ=';
|
|
68
|
+
afterEach(async ()=>{
|
|
69
|
+
// Cleanup: delete the test file if it exists
|
|
70
|
+
await app.inject({
|
|
71
|
+
method: 'DELETE',
|
|
72
|
+
url: testFilePath,
|
|
73
|
+
headers: {
|
|
74
|
+
authorization: auth
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
it('PUT with application/json should preserve stream and create file with content', async ()=>{
|
|
79
|
+
const jsonContent = '{"key":"value","number":42}';
|
|
80
|
+
const putRes = await app.inject({
|
|
81
|
+
method: 'PUT',
|
|
82
|
+
url: testFilePath,
|
|
83
|
+
headers: {
|
|
84
|
+
authorization: auth,
|
|
85
|
+
'content-type': 'application/json'
|
|
86
|
+
},
|
|
87
|
+
body: jsonContent
|
|
88
|
+
});
|
|
89
|
+
expect([
|
|
90
|
+
201,
|
|
91
|
+
204
|
|
92
|
+
]).toContain(putRes.statusCode);
|
|
93
|
+
// Verify the file was created with the correct content
|
|
94
|
+
const getRes = await app.inject({
|
|
95
|
+
method: 'GET',
|
|
96
|
+
url: testFilePath,
|
|
97
|
+
headers: {
|
|
98
|
+
authorization: auth
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
expect(getRes.statusCode).toEqual(200);
|
|
102
|
+
expect(getRes.body).toEqual(jsonContent);
|
|
103
|
+
expect(getRes.headers['content-length']).toEqual(String(jsonContent.length));
|
|
104
|
+
});
|
|
105
|
+
it('PUT with text/plain should preserve stream and create file with content', async ()=>{
|
|
106
|
+
const textContent = 'This is plain text content with special chars: éàù';
|
|
107
|
+
const putRes = await app.inject({
|
|
108
|
+
method: 'PUT',
|
|
109
|
+
url: testFilePath,
|
|
110
|
+
headers: {
|
|
111
|
+
authorization: auth,
|
|
112
|
+
'content-type': 'text/plain'
|
|
113
|
+
},
|
|
114
|
+
body: textContent
|
|
115
|
+
});
|
|
116
|
+
expect([
|
|
117
|
+
201,
|
|
118
|
+
204
|
|
119
|
+
]).toContain(putRes.statusCode);
|
|
120
|
+
// Verify the file was created with the correct content
|
|
121
|
+
const getRes = await app.inject({
|
|
122
|
+
method: 'GET',
|
|
123
|
+
url: testFilePath,
|
|
124
|
+
headers: {
|
|
125
|
+
authorization: auth
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
expect(getRes.statusCode).toEqual(200);
|
|
129
|
+
expect(getRes.body).toEqual(textContent);
|
|
130
|
+
expect(getRes.headers['content-length']).toEqual(String(Buffer.byteLength(textContent, 'utf8')));
|
|
131
|
+
});
|
|
132
|
+
it('PUT with text/plain; charset=utf-8 should preserve stream and create file with content', async ()=>{
|
|
133
|
+
const textContent = 'Text with charset and emoji: 🚀 ✅';
|
|
134
|
+
const putRes = await app.inject({
|
|
135
|
+
method: 'PUT',
|
|
136
|
+
url: testFilePath,
|
|
137
|
+
headers: {
|
|
138
|
+
authorization: auth,
|
|
139
|
+
'content-type': 'text/plain; charset=utf-8'
|
|
140
|
+
},
|
|
141
|
+
body: textContent
|
|
142
|
+
});
|
|
143
|
+
expect([
|
|
144
|
+
201,
|
|
145
|
+
204
|
|
146
|
+
]).toContain(putRes.statusCode);
|
|
147
|
+
// Verify the file was created with the correct content
|
|
148
|
+
const getRes = await app.inject({
|
|
149
|
+
method: 'GET',
|
|
150
|
+
url: testFilePath,
|
|
151
|
+
headers: {
|
|
152
|
+
authorization: auth
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
expect(getRes.statusCode).toEqual(200);
|
|
156
|
+
expect(getRes.body).toEqual(textContent);
|
|
157
|
+
expect(getRes.headers['content-length']).toEqual(String(Buffer.byteLength(textContent, 'utf8')));
|
|
158
|
+
});
|
|
159
|
+
it('PUT with application/octet-stream should work as expected', async ()=>{
|
|
160
|
+
const binaryContent = Buffer.from([
|
|
161
|
+
0x89,
|
|
162
|
+
0x50,
|
|
163
|
+
0x4e,
|
|
164
|
+
0x47,
|
|
165
|
+
0x0d,
|
|
166
|
+
0x0a,
|
|
167
|
+
0x1a,
|
|
168
|
+
0x0a
|
|
169
|
+
]);
|
|
170
|
+
const putRes = await app.inject({
|
|
171
|
+
method: 'PUT',
|
|
172
|
+
url: testFilePath,
|
|
173
|
+
headers: {
|
|
174
|
+
authorization: auth,
|
|
175
|
+
'content-type': 'application/octet-stream'
|
|
176
|
+
},
|
|
177
|
+
body: binaryContent
|
|
178
|
+
});
|
|
179
|
+
expect([
|
|
180
|
+
201,
|
|
181
|
+
204
|
|
182
|
+
]).toContain(putRes.statusCode);
|
|
183
|
+
// Verify the file was created with the correct content
|
|
184
|
+
const getRes = await app.inject({
|
|
185
|
+
method: 'GET',
|
|
186
|
+
url: testFilePath,
|
|
187
|
+
headers: {
|
|
188
|
+
authorization: auth
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
expect(getRes.statusCode).toEqual(200);
|
|
192
|
+
expect(Buffer.from(getRes.rawPayload)).toEqual(binaryContent);
|
|
193
|
+
expect(getRes.headers['content-length']).toEqual(String(binaryContent.length));
|
|
194
|
+
});
|
|
195
|
+
});
|
|
67
196
|
});
|
|
68
197
|
|
|
69
198
|
//# sourceMappingURL=webdav.e2e-spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/applications/webdav/webdav.e2e-spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { NestFastifyApplication } from '@nestjs/platform-fastify'\nimport { appBootstrap } from '../../app.bootstrap'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/applications/webdav/webdav.e2e-spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { NestFastifyApplication } from '@nestjs/platform-fastify'\nimport { appBootstrap } from '../../app.bootstrap'\nimport { XML_CONTENT_TYPE } from './constants/webdav'\n\nconst XML_VERSION_STR = '<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>'\n\ndescribe('WebDAV (e2e)', () => {\n let app: NestFastifyApplication\n\n beforeAll(async () => {\n app = await appBootstrap()\n await app.init()\n await app.getHttpAdapter().getInstance().ready()\n })\n\n afterAll(async () => {\n await app.close()\n })\n\n it('should be defined', () => {\n expect(app).toBeDefined()\n })\n\n it('PROPFIND ALLPROP /webdav => 207', async () => {\n const res = await app.inject({\n method: 'PROPFIND',\n url: '/webdav',\n headers: { authorization: 'Basic am86cGFzc3dvcmQ=', 'content-type': XML_CONTENT_TYPE, Depth: '1' },\n body: `${XML_VERSION_STR}\n <propfind xmlns:D=\"DAV:\">\n <allprop/>\n </propfind>`\n } as any)\n expect(res.statusCode).toEqual(207)\n })\n\n it('PROPFIND PROP /webdav => 207', async () => {\n const res = await app.inject({\n method: 'PROPFIND',\n url: '/webdav',\n headers: { authorization: 'Basic am86cGFzc3dvcmQ=', 'content-type': XML_CONTENT_TYPE, Depth: '1' },\n body: `${XML_VERSION_STR}\n <D:propfind xmlns:D=\"DAV:\">\n <D:prop>\n <D:creationdate/>\n <D:displayname/>\n <D:getcontentlength/>\n <D:getcontenttype/>\n <D:getetag/>\n <D:getlastmodified/>\n <D:resourcetype/>\n </D:prop>\n </D:propfind>`\n } as any)\n expect(res.statusCode).toEqual(207)\n })\n\n describe('PUT with non-XML Content-Types (stream preservation)', () => {\n const testFilePath = '/webdav/personal/test-content-type.txt'\n const auth = 'Basic am86cGFzc3dvcmQ='\n\n afterEach(async () => {\n // Cleanup: delete the test file if it exists\n await app.inject({\n method: 'DELETE',\n url: testFilePath,\n headers: { authorization: auth }\n } as any)\n })\n\n it('PUT with application/json should preserve stream and create file with content', async () => {\n const jsonContent = '{\"key\":\"value\",\"number\":42}'\n\n const putRes = await app.inject({\n method: 'PUT',\n url: testFilePath,\n headers: {\n authorization: auth,\n 'content-type': 'application/json'\n },\n body: jsonContent\n } as any)\n\n expect([201, 204]).toContain(putRes.statusCode)\n\n // Verify the file was created with the correct content\n const getRes = await app.inject({\n method: 'GET',\n url: testFilePath,\n headers: { authorization: auth }\n } as any)\n\n expect(getRes.statusCode).toEqual(200)\n expect(getRes.body).toEqual(jsonContent)\n expect(getRes.headers['content-length']).toEqual(String(jsonContent.length))\n })\n\n it('PUT with text/plain should preserve stream and create file with content', async () => {\n const textContent = 'This is plain text content with special chars: éàù'\n\n const putRes = await app.inject({\n method: 'PUT',\n url: testFilePath,\n headers: {\n authorization: auth,\n 'content-type': 'text/plain'\n },\n body: textContent\n } as any)\n\n expect([201, 204]).toContain(putRes.statusCode)\n\n // Verify the file was created with the correct content\n const getRes = await app.inject({\n method: 'GET',\n url: testFilePath,\n headers: { authorization: auth }\n } as any)\n\n expect(getRes.statusCode).toEqual(200)\n expect(getRes.body).toEqual(textContent)\n expect(getRes.headers['content-length']).toEqual(String(Buffer.byteLength(textContent, 'utf8')))\n })\n\n it('PUT with text/plain; charset=utf-8 should preserve stream and create file with content', async () => {\n const textContent = 'Text with charset and emoji: 🚀 ✅'\n\n const putRes = await app.inject({\n method: 'PUT',\n url: testFilePath,\n headers: {\n authorization: auth,\n 'content-type': 'text/plain; charset=utf-8'\n },\n body: textContent\n } as any)\n\n expect([201, 204]).toContain(putRes.statusCode)\n\n // Verify the file was created with the correct content\n const getRes = await app.inject({\n method: 'GET',\n url: testFilePath,\n headers: { authorization: auth }\n } as any)\n\n expect(getRes.statusCode).toEqual(200)\n expect(getRes.body).toEqual(textContent)\n expect(getRes.headers['content-length']).toEqual(String(Buffer.byteLength(textContent, 'utf8')))\n })\n\n it('PUT with application/octet-stream should work as expected', async () => {\n const binaryContent = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n\n const putRes = await app.inject({\n method: 'PUT',\n url: testFilePath,\n headers: {\n authorization: auth,\n 'content-type': 'application/octet-stream'\n },\n body: binaryContent\n } as any)\n\n expect([201, 204]).toContain(putRes.statusCode)\n\n // Verify the file was created with the correct content\n const getRes = await app.inject({\n method: 'GET',\n url: testFilePath,\n headers: { authorization: auth }\n } as any)\n\n expect(getRes.statusCode).toEqual(200)\n expect(Buffer.from(getRes.rawPayload)).toEqual(binaryContent)\n expect(getRes.headers['content-length']).toEqual(String(binaryContent.length))\n })\n })\n})\n"],"names":["XML_VERSION_STR","describe","app","beforeAll","appBootstrap","init","getHttpAdapter","getInstance","ready","afterAll","close","it","expect","toBeDefined","res","inject","method","url","headers","authorization","XML_CONTENT_TYPE","Depth","body","statusCode","toEqual","testFilePath","auth","afterEach","jsonContent","putRes","toContain","getRes","String","length","textContent","Buffer","byteLength","binaryContent","from","rawPayload"],"mappings":"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"}
|
|
@@ -42,7 +42,6 @@ describe('Auth (e2e)', ()=>{
|
|
|
42
42
|
deleteSpace: true,
|
|
43
43
|
isGuest: false
|
|
44
44
|
})).resolves.not.toThrow();
|
|
45
|
-
await (0, _utils.dbCloseConnection)(app);
|
|
46
45
|
await app.close();
|
|
47
46
|
});
|
|
48
47
|
it('should be defined', ()=>{
|
|
@@ -149,9 +148,8 @@ describe('Auth (e2e)', ()=>{
|
|
|
149
148
|
body: null
|
|
150
149
|
});
|
|
151
150
|
expect(res.statusCode).toEqual(201);
|
|
152
|
-
expect(res.headers['set-cookie']).toHaveLength(
|
|
153
|
-
|
|
154
|
-
/* Access cookie
|
|
151
|
+
expect(res.headers['set-cookie']).toHaveLength(5);
|
|
152
|
+
/* Access cookie
|
|
155
153
|
[
|
|
156
154
|
'sync-in-access=',
|
|
157
155
|
'Max-Age=0',
|
|
@@ -187,12 +185,20 @@ describe('Auth (e2e)', ()=>{
|
|
|
187
185
|
'Max-Age=0',
|
|
188
186
|
'Path=/api/auth/refresh',
|
|
189
187
|
'Expires=Thu, 01 Jan 1970 00:00:00 GMT',
|
|
188
|
+
'Secure',
|
|
189
|
+
'SameSite=Strict'
|
|
190
|
+
]
|
|
191
|
+
*/ /* Access cookie 2FA
|
|
192
|
+
[
|
|
193
|
+
'sync-in-access=',
|
|
194
|
+
'Max-Age=0',
|
|
195
|
+
'Path=/api/auth/2fa/login/verify',
|
|
196
|
+
'Expires=Thu, 01 Jan 1970 00:00:00 GMT',
|
|
190
197
|
'HttpOnly',
|
|
191
198
|
'Secure',
|
|
192
199
|
'SameSite=Strict'
|
|
193
200
|
]
|
|
194
|
-
*/
|
|
195
|
-
});
|
|
201
|
+
*/ });
|
|
196
202
|
it(`POST ${_routes.API_AUTH_REFRESH} => 201`, async ()=>{
|
|
197
203
|
const res = await app.inject({
|
|
198
204
|
method: 'POST',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../backend/src/authentication/auth.e2e-spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { ConfigService } from '@nestjs/config'\nimport { JwtService } from '@nestjs/jwt'\nimport { NestFastifyApplication } from '@nestjs/platform-fastify'\nimport { appBootstrap } from '../app.bootstrap'\nimport { USER_ROLE } from '../applications/users/constants/user'\nimport { DeleteUserDto } from '../applications/users/dto/delete-user.dto'\nimport { UserModel } from '../applications/users/models/user.model'\nimport { AdminUsersManager } from '../applications/users/services/admin-users-manager.service'\nimport { generateUserTest } from '../applications/users/utils/test'\nimport { convertHumanTimeToSeconds, transformAndValidate } from '../common/functions'\nimport { currentTimeStamp, decodeUrl } from '../common/shared'\nimport { dbCheckConnection, dbCloseConnection } from '../infrastructure/database/utils'\nimport { AuthConfig } from './auth.config'\nimport { CSRF_ERROR, TOKEN_PATHS, TOKEN_TYPES } from './constants/auth'\nimport { API_AUTH_LOGIN, API_AUTH_LOGOUT, API_AUTH_REFRESH, API_AUTH_TOKEN, API_AUTH_TOKEN_REFRESH } from './constants/routes'\nimport { TokenResponseDto } from './dto/token-response.dto'\nimport { JwtPayload } from './interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from './interfaces/token.interface'\n\ndescribe('Auth (e2e)', () => {\n let app: NestFastifyApplication\n let authConfig: AuthConfig\n let jwtService: JwtService\n let adminUsersManager: AdminUsersManager\n let userTest: UserModel\n let refreshToken: string\n let csrfToken: string\n\n beforeAll(async () => {\n app = await appBootstrap()\n await app.init()\n await app.getHttpAdapter().getInstance().ready()\n authConfig = app.get<ConfigService>(ConfigService).get<AuthConfig>('auth')\n jwtService = app.get<JwtService>(JwtService)\n adminUsersManager = app.get<AdminUsersManager>(AdminUsersManager)\n userTest = new UserModel(generateUserTest(false), false)\n })\n\n afterAll(async () => {\n await expect(\n adminUsersManager.deleteUserOrGuest(userTest.id, userTest.login, { deleteSpace: true, isGuest: false } satisfies DeleteUserDto)\n ).resolves.not.toThrow()\n await dbCloseConnection(app)\n await app.close()\n })\n\n it('should be defined', () => {\n expect(authConfig).toBeDefined()\n expect(jwtService).toBeDefined()\n expect(adminUsersManager).toBeDefined()\n expect(userTest).toBeDefined()\n })\n\n it('should get the database connection', async () => {\n expect(await dbCheckConnection(app)).toBe(true)\n })\n\n it(`POST ${API_AUTH_LOGIN} => 401`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_LOGIN,\n body: { login: userTest.login, password: userTest.password }\n })\n expect(res.statusCode).toEqual(401)\n })\n\n it(`POST ${API_AUTH_LOGIN} => 201`, async () => {\n const userId = (await adminUsersManager.createUserOrGuest({ ...userTest }, USER_ROLE.USER)).id\n expect(userId).toBeDefined()\n userTest.id = userId\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_LOGIN,\n body: { login: userTest.login, password: userTest.password }\n })\n expect(res.statusCode).toEqual(201)\n expect(Object.keys(res.json())).toEqual(expect.arrayContaining(['user', 'token']))\n expect(res.headers['set-cookie']).toHaveLength(4)\n const cookies: { type: TOKEN_TYPE; content: string[] }[] = getCookies(res.headers['set-cookie'] as string[])\n /* Access cookie\n [\n 'sync-in-access=value,\n 'Max-Age=3600',\n 'Path=/',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* Refresh cookie\n [\n 'sync-in-refresh=value,\n 'Max-Age=14400',\n 'Path=/api/auth/refresh',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* WS cookie\n [\n 'sync-in-ws=value,\n 'Max-Age=14400',\n 'Path=/socket.io',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* CSRF cookie\n [\n 'sync-in-csrf=value,\n 'Max-Age=14400',\n 'Path=/',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n cookiesChecks(cookies)\n // Verify token\n for (const cookie of cookies) {\n const token = cookie.content[0].substring(cookie.content[0].indexOf('=') + 1)\n if (cookie.type === TOKEN_TYPE.CSRF) {\n // needed for the following tests\n csrfToken = decodeUrl(token)\n continue\n }\n const decodedToken: JwtPayload = await jwtService.verifyAsync(token, {\n secret: authConfig.token[cookie.type].secret\n })\n expect(decodedToken.iat).toBeCloseTo(currentTimeStamp(), -1)\n expect(decodedToken.exp).toBeCloseTo(currentTimeStamp() + convertHumanTimeToSeconds(authConfig.token[cookie.type].expiration), -1)\n expect(decodedToken.identity.id).toBe(userTest.id)\n if (cookie.type === TOKEN_TYPE.REFRESH) {\n // needed for the following tests\n refreshToken = token\n }\n }\n })\n\n it(`POST ${API_AUTH_LOGOUT} => 201`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_LOGOUT,\n body: null\n })\n expect(res.statusCode).toEqual(201)\n expect(res.headers['set-cookie']).toHaveLength(4)\n const cookies: { type: TOKEN_TYPE; content: string[] }[] = getCookies(res.headers['set-cookie'] as string[])\n /* Access cookie\n [\n 'sync-in-access=',\n 'Max-Age=0',\n 'Path=/',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* Refresh cookie\n [\n 'sync-in-refresh=',\n 'Max-Age=0',\n 'Path=/api/auth/refresh',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* WS cookie\n [\n 'sync-in-ws=',\n 'Max-Age=0',\n 'Path=/socket.io',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* CSRF cookie\n [\n 'sync-in-csrf=',\n 'Max-Age=0',\n 'Path=/api/auth/refresh',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n cookiesChecks(cookies, true)\n })\n\n it(`POST ${API_AUTH_REFRESH} => 201`, async () => {\n const res = await app.inject({\n method: 'POST',\n headers: { [authConfig.token.csrf.name]: csrfToken },\n url: API_AUTH_REFRESH,\n cookies: { [authConfig.token.refresh.name]: refreshToken }\n })\n expect(res.statusCode).toEqual(201)\n const cookies: { type: TOKEN_TYPE; content: string[] }[] = getCookies(res.headers['set-cookie'] as string[])\n cookiesChecks(cookies)\n })\n\n it(`POST ${API_AUTH_REFRESH} => 401 (with CSRF)`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_REFRESH,\n headers: { [authConfig.token.csrf.name]: csrfToken },\n cookies: { [authConfig.token.refresh.name]: 'bar' }\n })\n expect(res.statusCode).toEqual(401)\n })\n\n it(`POST ${API_AUTH_REFRESH} => 403 (without CSRF)`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_REFRESH,\n cookies: { [authConfig.token.refresh.name]: refreshToken }\n })\n expect(res.statusCode).toEqual(403)\n expect(res.json().message).toEqual(CSRF_ERROR.MISSING_HEADERS)\n })\n\n it(`POST ${API_AUTH_TOKEN} => 401`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_TOKEN,\n body: { login: userTest.login, password: 'bar' }\n })\n expect(res.statusCode).toEqual(401)\n })\n\n it(`POST ${API_AUTH_TOKEN} => 201`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_TOKEN,\n body: { login: userTest.login, password: userTest.password }\n })\n expect(res.statusCode).toEqual(201)\n const content = res.json()\n expect(() => transformAndValidate(TokenResponseDto, content)).not.toThrow()\n for (const type of TOKEN_TYPES.filter((p) => p === TOKEN_TYPE.ACCESS || p === TOKEN_TYPE.REFRESH)) {\n expect(content[type]).toBeDefined()\n expect(content[`${type}_expiration`]).toBeCloseTo(currentTimeStamp() + convertHumanTimeToSeconds(authConfig.token[type].expiration), -1)\n }\n })\n\n it(`POST ${API_AUTH_TOKEN_REFRESH} => 401`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_TOKEN_REFRESH,\n headers: { authorization: 'Bearer bar' }\n })\n expect(res.statusCode).toEqual(401)\n })\n\n it(`POST ${API_AUTH_TOKEN_REFRESH} => 201`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_TOKEN_REFRESH,\n headers: { authorization: `Bearer ${refreshToken}` }\n })\n expect(res.statusCode).toEqual(201)\n expect(() => transformAndValidate(TokenResponseDto, res.json())).not.toThrow()\n })\n\n function getCookies(setCookie: string[]): { type: TOKEN_TYPE; content: string[] }[] {\n const cookies: { type: TOKEN_TYPE; content: string[] }[] = []\n for (const c of setCookie) {\n const cookieName = c.split('=')[0]\n const cookieValues = c.split('; ')\n switch (cookieName) {\n case authConfig.token.access.name:\n cookies.push({ type: TOKEN_TYPE.ACCESS, content: cookieValues })\n break\n case authConfig.token.refresh.name:\n cookies.push({ type: TOKEN_TYPE.REFRESH, content: cookieValues })\n break\n case authConfig.token.ws.name:\n cookies.push({ type: TOKEN_TYPE.WS, content: cookieValues })\n break\n case authConfig.token.csrf.name:\n cookies.push({ type: TOKEN_TYPE.CSRF, content: cookieValues })\n break\n }\n }\n return cookies\n }\n\n function cookiesChecks(cookies: { type: TOKEN_TYPE; content: string[] }[], clear = false) {\n for (const cookie of cookies) {\n expect(cookie.content[0].split('=')[0]).toBe(authConfig.token[cookie.type].name)\n expect(cookie.content[2].split('=')[1]).toBe(TOKEN_PATHS[cookie.type])\n if (cookie.type === TOKEN_TYPE.CSRF) {\n expect(cookie.content).not.toContain('HttpOnly')\n } else {\n expect(cookie.content).toContain('HttpOnly')\n }\n expect(cookie.content).not.toContain('Secure')\n expect(cookie.content[cookie.content.length - 1].split('=')[1].toLowerCase()).toBe(authConfig.cookieSameSite)\n if (clear) {\n expect(cookie.content[0].split('=')[1]).toBe('')\n expect(cookie.content[1].split('=')[1]).toBe('0')\n expect(cookie.content[3].split('=')[1]).toBe('Thu, 01 Jan 1970 00:00:00 GMT')\n } else {\n expect(parseInt(cookie.content[1].split('=')[1])).toBeCloseTo(convertHumanTimeToSeconds(authConfig.token[cookie.type].expiration), -1)\n expect(cookie.content[0].split('=')[1]).not.toBe('')\n }\n }\n }\n})\n"],"names":["describe","app","authConfig","jwtService","adminUsersManager","userTest","refreshToken","csrfToken","beforeAll","appBootstrap","init","getHttpAdapter","getInstance","ready","get","ConfigService","JwtService","AdminUsersManager","UserModel","generateUserTest","afterAll","expect","deleteUserOrGuest","id","login","deleteSpace","isGuest","resolves","not","toThrow","dbCloseConnection","close","it","toBeDefined","dbCheckConnection","toBe","API_AUTH_LOGIN","res","inject","method","url","body","password","statusCode","toEqual","userId","createUserOrGuest","USER_ROLE","USER","Object","keys","json","arrayContaining","headers","toHaveLength","cookies","getCookies","cookiesChecks","cookie","token","content","substring","indexOf","type","TOKEN_TYPE","CSRF","decodeUrl","decodedToken","verifyAsync","secret","iat","toBeCloseTo","currentTimeStamp","exp","convertHumanTimeToSeconds","expiration","identity","REFRESH","API_AUTH_LOGOUT","API_AUTH_REFRESH","csrf","name","refresh","message","CSRF_ERROR","MISSING_HEADERS","API_AUTH_TOKEN","transformAndValidate","TokenResponseDto","TOKEN_TYPES","filter","p","ACCESS","API_AUTH_TOKEN_REFRESH","authorization","setCookie","c","cookieName","split","cookieValues","access","push","ws","WS","clear","TOKEN_PATHS","toContain","length","toLowerCase","cookieSameSite","parseInt"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../backend/src/authentication/auth.e2e-spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { ConfigService } from '@nestjs/config'\nimport { JwtService } from '@nestjs/jwt'\nimport { NestFastifyApplication } from '@nestjs/platform-fastify'\nimport { appBootstrap } from '../app.bootstrap'\nimport { USER_ROLE } from '../applications/users/constants/user'\nimport { DeleteUserDto } from '../applications/users/dto/delete-user.dto'\nimport { UserModel } from '../applications/users/models/user.model'\nimport { AdminUsersManager } from '../applications/users/services/admin-users-manager.service'\nimport { generateUserTest } from '../applications/users/utils/test'\nimport { convertHumanTimeToSeconds, transformAndValidate } from '../common/functions'\nimport { currentTimeStamp, decodeUrl } from '../common/shared'\nimport { dbCheckConnection } from '../infrastructure/database/utils'\nimport { AuthConfig } from './auth.config'\nimport { CSRF_ERROR, TOKEN_PATHS, TOKEN_TYPES } from './constants/auth'\nimport { API_AUTH_LOGIN, API_AUTH_LOGOUT, API_AUTH_REFRESH, API_AUTH_TOKEN, API_AUTH_TOKEN_REFRESH } from './constants/routes'\nimport { TokenResponseDto } from './dto/token-response.dto'\nimport { JwtPayload } from './interfaces/jwt-payload.interface'\nimport { TOKEN_TYPE } from './interfaces/token.interface'\n\ndescribe('Auth (e2e)', () => {\n let app: NestFastifyApplication\n let authConfig: AuthConfig\n let jwtService: JwtService\n let adminUsersManager: AdminUsersManager\n let userTest: UserModel\n let refreshToken: string\n let csrfToken: string\n\n beforeAll(async () => {\n app = await appBootstrap()\n await app.init()\n await app.getHttpAdapter().getInstance().ready()\n authConfig = app.get<ConfigService>(ConfigService).get<AuthConfig>('auth')\n jwtService = app.get<JwtService>(JwtService)\n adminUsersManager = app.get<AdminUsersManager>(AdminUsersManager)\n userTest = new UserModel(generateUserTest(false), false)\n })\n\n afterAll(async () => {\n await expect(\n adminUsersManager.deleteUserOrGuest(userTest.id, userTest.login, { deleteSpace: true, isGuest: false } satisfies DeleteUserDto)\n ).resolves.not.toThrow()\n await app.close()\n })\n\n it('should be defined', () => {\n expect(authConfig).toBeDefined()\n expect(jwtService).toBeDefined()\n expect(adminUsersManager).toBeDefined()\n expect(userTest).toBeDefined()\n })\n\n it('should get the database connection', async () => {\n expect(await dbCheckConnection(app)).toBe(true)\n })\n\n it(`POST ${API_AUTH_LOGIN} => 401`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_LOGIN,\n body: { login: userTest.login, password: userTest.password }\n })\n expect(res.statusCode).toEqual(401)\n })\n\n it(`POST ${API_AUTH_LOGIN} => 201`, async () => {\n const userId = (await adminUsersManager.createUserOrGuest({ ...userTest }, USER_ROLE.USER)).id\n expect(userId).toBeDefined()\n userTest.id = userId\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_LOGIN,\n body: { login: userTest.login, password: userTest.password }\n })\n expect(res.statusCode).toEqual(201)\n expect(Object.keys(res.json())).toEqual(expect.arrayContaining(['user', 'token']))\n expect(res.headers['set-cookie']).toHaveLength(4)\n const cookies: { type: TOKEN_TYPE; content: string[] }[] = getCookies(res.headers['set-cookie'] as string[])\n /* Access cookie\n [\n 'sync-in-access=value,\n 'Max-Age=3600',\n 'Path=/',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* Refresh cookie\n [\n 'sync-in-refresh=value,\n 'Max-Age=14400',\n 'Path=/api/auth/refresh',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* WS cookie\n [\n 'sync-in-ws=value,\n 'Max-Age=14400',\n 'Path=/socket.io',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* CSRF cookie\n [\n 'sync-in-csrf=value,\n 'Max-Age=14400',\n 'Path=/',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n cookiesChecks(cookies)\n // Verify token\n for (const cookie of cookies) {\n const token = cookie.content[0].substring(cookie.content[0].indexOf('=') + 1)\n if (cookie.type === TOKEN_TYPE.CSRF) {\n // needed for the following tests\n csrfToken = decodeUrl(token)\n continue\n }\n const decodedToken: JwtPayload = await jwtService.verifyAsync(token, {\n secret: authConfig.token[cookie.type].secret\n })\n expect(decodedToken.iat).toBeCloseTo(currentTimeStamp(), -1)\n expect(decodedToken.exp).toBeCloseTo(currentTimeStamp() + convertHumanTimeToSeconds(authConfig.token[cookie.type].expiration), -1)\n expect(decodedToken.identity.id).toBe(userTest.id)\n if (cookie.type === TOKEN_TYPE.REFRESH) {\n // needed for the following tests\n refreshToken = token\n }\n }\n })\n\n it(`POST ${API_AUTH_LOGOUT} => 201`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_LOGOUT,\n body: null\n })\n expect(res.statusCode).toEqual(201)\n expect(res.headers['set-cookie']).toHaveLength(5)\n /* Access cookie\n [\n 'sync-in-access=',\n 'Max-Age=0',\n 'Path=/',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* Refresh cookie\n [\n 'sync-in-refresh=',\n 'Max-Age=0',\n 'Path=/api/auth/refresh',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* WS cookie\n [\n 'sync-in-ws=',\n 'Max-Age=0',\n 'Path=/socket.io',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* CSRF cookie\n [\n 'sync-in-csrf=',\n 'Max-Age=0',\n 'Path=/api/auth/refresh',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n /* Access cookie 2FA\n [\n 'sync-in-access=',\n 'Max-Age=0',\n 'Path=/api/auth/2fa/login/verify',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT',\n 'HttpOnly',\n 'Secure',\n 'SameSite=Strict'\n ]\n */\n })\n\n it(`POST ${API_AUTH_REFRESH} => 201`, async () => {\n const res = await app.inject({\n method: 'POST',\n headers: { [authConfig.token.csrf.name]: csrfToken },\n url: API_AUTH_REFRESH,\n cookies: { [authConfig.token.refresh.name]: refreshToken }\n })\n expect(res.statusCode).toEqual(201)\n const cookies: { type: TOKEN_TYPE; content: string[] }[] = getCookies(res.headers['set-cookie'] as string[])\n cookiesChecks(cookies)\n })\n\n it(`POST ${API_AUTH_REFRESH} => 401 (with CSRF)`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_REFRESH,\n headers: { [authConfig.token.csrf.name]: csrfToken },\n cookies: { [authConfig.token.refresh.name]: 'bar' }\n })\n expect(res.statusCode).toEqual(401)\n })\n\n it(`POST ${API_AUTH_REFRESH} => 403 (without CSRF)`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_REFRESH,\n cookies: { [authConfig.token.refresh.name]: refreshToken }\n })\n expect(res.statusCode).toEqual(403)\n expect(res.json().message).toEqual(CSRF_ERROR.MISSING_HEADERS)\n })\n\n it(`POST ${API_AUTH_TOKEN} => 401`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_TOKEN,\n body: { login: userTest.login, password: 'bar' }\n })\n expect(res.statusCode).toEqual(401)\n })\n\n it(`POST ${API_AUTH_TOKEN} => 201`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_TOKEN,\n body: { login: userTest.login, password: userTest.password }\n })\n expect(res.statusCode).toEqual(201)\n const content = res.json()\n expect(() => transformAndValidate(TokenResponseDto, content)).not.toThrow()\n for (const type of TOKEN_TYPES.filter((p) => p === TOKEN_TYPE.ACCESS || p === TOKEN_TYPE.REFRESH)) {\n expect(content[type]).toBeDefined()\n expect(content[`${type}_expiration`]).toBeCloseTo(currentTimeStamp() + convertHumanTimeToSeconds(authConfig.token[type].expiration), -1)\n }\n })\n\n it(`POST ${API_AUTH_TOKEN_REFRESH} => 401`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_TOKEN_REFRESH,\n headers: { authorization: 'Bearer bar' }\n })\n expect(res.statusCode).toEqual(401)\n })\n\n it(`POST ${API_AUTH_TOKEN_REFRESH} => 201`, async () => {\n const res = await app.inject({\n method: 'POST',\n url: API_AUTH_TOKEN_REFRESH,\n headers: { authorization: `Bearer ${refreshToken}` }\n })\n expect(res.statusCode).toEqual(201)\n expect(() => transformAndValidate(TokenResponseDto, res.json())).not.toThrow()\n })\n\n function getCookies(setCookie: string[]): { type: TOKEN_TYPE; content: string[] }[] {\n const cookies: { type: TOKEN_TYPE; content: string[] }[] = []\n for (const c of setCookie) {\n const cookieName = c.split('=')[0]\n const cookieValues = c.split('; ')\n switch (cookieName) {\n case authConfig.token.access.name:\n cookies.push({ type: TOKEN_TYPE.ACCESS, content: cookieValues })\n break\n case authConfig.token.refresh.name:\n cookies.push({ type: TOKEN_TYPE.REFRESH, content: cookieValues })\n break\n case authConfig.token.ws.name:\n cookies.push({ type: TOKEN_TYPE.WS, content: cookieValues })\n break\n case authConfig.token.csrf.name:\n cookies.push({ type: TOKEN_TYPE.CSRF, content: cookieValues })\n break\n }\n }\n return cookies\n }\n\n function cookiesChecks(cookies: { type: TOKEN_TYPE; content: string[] }[], clear = false) {\n for (const cookie of cookies) {\n expect(cookie.content[0].split('=')[0]).toBe(authConfig.token[cookie.type].name)\n expect(cookie.content[2].split('=')[1]).toBe(TOKEN_PATHS[cookie.type])\n if (cookie.type === TOKEN_TYPE.CSRF) {\n expect(cookie.content).not.toContain('HttpOnly')\n } else {\n expect(cookie.content).toContain('HttpOnly')\n }\n expect(cookie.content).not.toContain('Secure')\n expect(cookie.content[cookie.content.length - 1].split('=')[1].toLowerCase()).toBe(authConfig.cookieSameSite)\n if (clear) {\n expect(cookie.content[0].split('=')[1]).toBe('')\n expect(cookie.content[1].split('=')[1]).toBe('0')\n expect(cookie.content[3].split('=')[1]).toBe('Thu, 01 Jan 1970 00:00:00 GMT')\n } else {\n expect(parseInt(cookie.content[1].split('=')[1])).toBeCloseTo(convertHumanTimeToSeconds(authConfig.token[cookie.type].expiration), -1)\n expect(cookie.content[0].split('=')[1]).not.toBe('')\n }\n }\n }\n})\n"],"names":["describe","app","authConfig","jwtService","adminUsersManager","userTest","refreshToken","csrfToken","beforeAll","appBootstrap","init","getHttpAdapter","getInstance","ready","get","ConfigService","JwtService","AdminUsersManager","UserModel","generateUserTest","afterAll","expect","deleteUserOrGuest","id","login","deleteSpace","isGuest","resolves","not","toThrow","close","it","toBeDefined","dbCheckConnection","toBe","API_AUTH_LOGIN","res","inject","method","url","body","password","statusCode","toEqual","userId","createUserOrGuest","USER_ROLE","USER","Object","keys","json","arrayContaining","headers","toHaveLength","cookies","getCookies","cookiesChecks","cookie","token","content","substring","indexOf","type","TOKEN_TYPE","CSRF","decodeUrl","decodedToken","verifyAsync","secret","iat","toBeCloseTo","currentTimeStamp","exp","convertHumanTimeToSeconds","expiration","identity","REFRESH","API_AUTH_LOGOUT","API_AUTH_REFRESH","csrf","name","refresh","message","CSRF_ERROR","MISSING_HEADERS","API_AUTH_TOKEN","transformAndValidate","TokenResponseDto","TOKEN_TYPES","filter","p","ACCESS","API_AUTH_TOKEN_REFRESH","authorization","setCookie","c","cookieName","split","cookieValues","access","push","ws","WS","clear","TOKEN_PATHS","toContain","length","toLowerCase","cookieSameSite","parseInt"],"mappings":"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"}
|
|
@@ -82,6 +82,29 @@ describe(_authbasicguard.AuthBasicGuard.name, ()=>{
|
|
|
82
82
|
expect(await authBasicGuard.canActivate(context)).toBe(true);
|
|
83
83
|
expect(userTest.password).toBeUndefined();
|
|
84
84
|
});
|
|
85
|
+
it('should validate the user authentication with password containing colon', async ()=>{
|
|
86
|
+
const passwordWithColon = 'pass:word:123';
|
|
87
|
+
const userWithColonPassword = new _usermodel.UserModel({
|
|
88
|
+
...(0, _test.generateUserTest)(),
|
|
89
|
+
password: passwordWithColon
|
|
90
|
+
}, false);
|
|
91
|
+
const encodedAuthWithColon = Buffer.from(`${userWithColonPassword.login}:${passwordWithColon}`).toString('base64');
|
|
92
|
+
authMethod.validateUser = jest.fn().mockImplementation((login, password)=>{
|
|
93
|
+
expect(login).toBe(userWithColonPassword.login);
|
|
94
|
+
expect(password).toBe(passwordWithColon);
|
|
95
|
+
return userWithColonPassword;
|
|
96
|
+
});
|
|
97
|
+
context.switchToHttp().getRequest.mockReturnValue({
|
|
98
|
+
raw: {
|
|
99
|
+
user: ''
|
|
100
|
+
},
|
|
101
|
+
headers: {
|
|
102
|
+
authorization: `Basic ${encodedAuthWithColon}`
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
expect(await authBasicGuard.canActivate(context)).toBe(true);
|
|
106
|
+
expect(userWithColonPassword.password).toBeUndefined();
|
|
107
|
+
});
|
|
85
108
|
it('should validate the user authentication with cache', async ()=>{
|
|
86
109
|
cache.get = jest.fn().mockReturnValueOnce(userTest);
|
|
87
110
|
context.switchToHttp().getRequest.mockReturnValue({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext } from '@nestjs/common'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { PinoLogger } from 'nestjs-pino'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { generateUserTest } from '../../applications/users/utils/test'\nimport { WEBDAV_BASE_PATH } from '../../applications/webdav/constants/routes'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AuthMethod } from '../models/auth-method'\nimport { AuthBasicGuard } from './auth-basic.guard'\nimport { AuthBasicStrategy } from './auth-basic.strategy'\n\ndescribe(AuthBasicGuard.name, () => {\n let authBasicGuard: AuthBasicGuard\n let authBasicStrategy: AuthBasicStrategy\n let authMethod: AuthMethod\n let cache: Cache\n let userTest: UserModel\n let encodedAuth: string\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthBasicGuard,\n AuthBasicStrategy,\n {\n provide: AuthMethod,\n useValue: {\n validateUser: async () => null\n }\n },\n {\n provide: PinoLogger,\n useValue: {\n assign: () => undefined,\n error: jest.fn()\n }\n },\n {\n provide: Cache,\n useValue: {\n get: (_key: string) => undefined,\n set: async (_key: string, _value: string, _ttl: number) => undefined,\n genSlugKey: () => 'test'\n }\n }\n ]\n }).compile()\n\n authBasicGuard = module.get<AuthBasicGuard>(AuthBasicGuard)\n authBasicStrategy = module.get<AuthBasicStrategy>(AuthBasicStrategy)\n authMethod = module.get<AuthMethod>(AuthMethod)\n cache = module.get<Cache>(Cache)\n userTest = new UserModel(generateUserTest(), false)\n encodedAuth = Buffer.from(`${userTest.login}:${userTest.password}`).toString('base64')\n context = createMock<ExecutionContext>()\n })\n\n it('should be defined', () => {\n expect(authBasicGuard).toBeDefined()\n expect(authBasicStrategy).toBeDefined()\n expect(authMethod).toBeDefined()\n expect(cache).toBeDefined()\n expect(encodedAuth).toBeDefined()\n expect(userTest).toBeDefined()\n expect(userTest.password).toBeDefined()\n })\n\n it('should validate the user authentication', async () => {\n authMethod.validateUser = jest.fn().mockReturnValueOnce(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n expect(userTest.password).toBeUndefined()\n })\n\n it('should validate the user authentication with cache', async () => {\n cache.get = jest.fn().mockReturnValueOnce(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n })\n\n it('should not validate the user authentication when cache returns null (explicitly unauthorized)', async () => {\n cache.get = jest.fn().mockReturnValueOnce(null)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it('should not validate the user authentication when cache returns undefined and database return null', async () => {\n cache.get = jest.fn().mockReturnValueOnce(undefined)\n authMethod.validateUser = jest.fn().mockReturnValueOnce(null)\n jest.spyOn(cache, 'set').mockRejectedValueOnce(new Error('cache failed'))\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n const loggerSpy = jest\n .spyOn(authBasicStrategy['logger'], 'error') // <-- spy the SAME instance used in the class\n .mockImplementation(() => undefined)\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n expect(loggerSpy).toHaveBeenCalled()\n expect(loggerSpy.mock.calls[0][0]).toEqual(expect.stringContaining('cache failed'))\n })\n\n it('should not validate the user authentication', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it('should throw error due to malformed authorization header', async () => {\n // headers with capitals not working\n context.switchToHttp().getRequest.mockReturnValueOnce({\n raw: { user: '' },\n headers: { AUTHORIZATION: 'Basic foo' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n context.switchToHttp().getRequest.mockReturnValueOnce({\n raw: { user: '' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it(`should valid OPTIONS method without authentication header on \"/\" and \"/${WEBDAV_BASE_PATH}/*\" paths `, async () => {\n for (const url of ['', `/${WEBDAV_BASE_PATH}`, `/${WEBDAV_BASE_PATH}/foo/bar`]) {\n context.switchToHttp().getRequest.mockReturnValueOnce({\n method: 'OPTIONS',\n originalUrl: url,\n raw: { user: '' }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n }\n })\n\n it('should not valid OPTIONS method with other paths', async () => {\n context.switchToHttp().getRequest.mockReturnValueOnce({\n method: 'OPTIONS',\n originalUrl: '/foo',\n raw: { user: '' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n})\n"],"names":["describe","AuthBasicGuard","name","authBasicGuard","authBasicStrategy","authMethod","cache","userTest","encodedAuth","context","beforeAll","module","Test","createTestingModule","providers","AuthBasicStrategy","provide","AuthMethod","useValue","validateUser","PinoLogger","assign","undefined","error","jest","fn","Cache","get","_key","set","_value","_ttl","genSlugKey","compile","UserModel","generateUserTest","Buffer","from","login","password","toString","createMock","it","expect","toBeDefined","mockReturnValueOnce","switchToHttp","getRequest","mockReturnValue","raw","user","headers","authorization","canActivate","toBe","toBeUndefined","rejects","toThrow","spyOn","mockRejectedValueOnce","Error","loggerSpy","mockImplementation","toHaveBeenCalled","mock","calls","toEqual","stringContaining","AUTHORIZATION","WEBDAV_BASE_PATH","url","method","originalUrl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.guard.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { createMock, DeepMocked } from '@golevelup/ts-jest'\nimport { ExecutionContext } from '@nestjs/common'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { PinoLogger } from 'nestjs-pino'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { generateUserTest } from '../../applications/users/utils/test'\nimport { WEBDAV_BASE_PATH } from '../../applications/webdav/constants/routes'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AuthMethod } from '../models/auth-method'\nimport { AuthBasicGuard } from './auth-basic.guard'\nimport { AuthBasicStrategy } from './auth-basic.strategy'\n\ndescribe(AuthBasicGuard.name, () => {\n let authBasicGuard: AuthBasicGuard\n let authBasicStrategy: AuthBasicStrategy\n let authMethod: AuthMethod\n let cache: Cache\n let userTest: UserModel\n let encodedAuth: string\n let context: DeepMocked<ExecutionContext>\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthBasicGuard,\n AuthBasicStrategy,\n {\n provide: AuthMethod,\n useValue: {\n validateUser: async () => null\n }\n },\n {\n provide: PinoLogger,\n useValue: {\n assign: () => undefined,\n error: jest.fn()\n }\n },\n {\n provide: Cache,\n useValue: {\n get: (_key: string) => undefined,\n set: async (_key: string, _value: string, _ttl: number) => undefined,\n genSlugKey: () => 'test'\n }\n }\n ]\n }).compile()\n\n authBasicGuard = module.get<AuthBasicGuard>(AuthBasicGuard)\n authBasicStrategy = module.get<AuthBasicStrategy>(AuthBasicStrategy)\n authMethod = module.get<AuthMethod>(AuthMethod)\n cache = module.get<Cache>(Cache)\n userTest = new UserModel(generateUserTest(), false)\n encodedAuth = Buffer.from(`${userTest.login}:${userTest.password}`).toString('base64')\n context = createMock<ExecutionContext>()\n })\n\n it('should be defined', () => {\n expect(authBasicGuard).toBeDefined()\n expect(authBasicStrategy).toBeDefined()\n expect(authMethod).toBeDefined()\n expect(cache).toBeDefined()\n expect(encodedAuth).toBeDefined()\n expect(userTest).toBeDefined()\n expect(userTest.password).toBeDefined()\n })\n\n it('should validate the user authentication', async () => {\n authMethod.validateUser = jest.fn().mockReturnValueOnce(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n expect(userTest.password).toBeUndefined()\n })\n\n it('should validate the user authentication with password containing colon', async () => {\n const passwordWithColon = 'pass:word:123'\n const userWithColonPassword = new UserModel({ ...generateUserTest(), password: passwordWithColon }, false)\n const encodedAuthWithColon = Buffer.from(`${userWithColonPassword.login}:${passwordWithColon}`).toString('base64')\n\n authMethod.validateUser = jest.fn().mockImplementation((login: string, password: string) => {\n expect(login).toBe(userWithColonPassword.login)\n expect(password).toBe(passwordWithColon)\n return userWithColonPassword\n })\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuthWithColon}` }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n expect(userWithColonPassword.password).toBeUndefined()\n })\n\n it('should validate the user authentication with cache', async () => {\n cache.get = jest.fn().mockReturnValueOnce(userTest)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n })\n\n it('should not validate the user authentication when cache returns null (explicitly unauthorized)', async () => {\n cache.get = jest.fn().mockReturnValueOnce(null)\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it('should not validate the user authentication when cache returns undefined and database return null', async () => {\n cache.get = jest.fn().mockReturnValueOnce(undefined)\n authMethod.validateUser = jest.fn().mockReturnValueOnce(null)\n jest.spyOn(cache, 'set').mockRejectedValueOnce(new Error('cache failed'))\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n const loggerSpy = jest\n .spyOn(authBasicStrategy['logger'], 'error') // <-- spy the SAME instance used in the class\n .mockImplementation(() => undefined)\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n expect(loggerSpy).toHaveBeenCalled()\n expect(loggerSpy.mock.calls[0][0]).toEqual(expect.stringContaining('cache failed'))\n })\n\n it('should not validate the user authentication', async () => {\n context.switchToHttp().getRequest.mockReturnValue({\n raw: { user: '' },\n headers: { authorization: `Basic ${encodedAuth}` }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it('should throw error due to malformed authorization header', async () => {\n // headers with capitals not working\n context.switchToHttp().getRequest.mockReturnValueOnce({\n raw: { user: '' },\n headers: { AUTHORIZATION: 'Basic foo' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n context.switchToHttp().getRequest.mockReturnValueOnce({\n raw: { user: '' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n\n it(`should valid OPTIONS method without authentication header on \"/\" and \"/${WEBDAV_BASE_PATH}/*\" paths `, async () => {\n for (const url of ['', `/${WEBDAV_BASE_PATH}`, `/${WEBDAV_BASE_PATH}/foo/bar`]) {\n context.switchToHttp().getRequest.mockReturnValueOnce({\n method: 'OPTIONS',\n originalUrl: url,\n raw: { user: '' }\n })\n expect(await authBasicGuard.canActivate(context)).toBe(true)\n }\n })\n\n it('should not valid OPTIONS method with other paths', async () => {\n context.switchToHttp().getRequest.mockReturnValueOnce({\n method: 'OPTIONS',\n originalUrl: '/foo',\n raw: { user: '' }\n })\n await expect(authBasicGuard.canActivate(context)).rejects.toThrow()\n })\n})\n"],"names":["describe","AuthBasicGuard","name","authBasicGuard","authBasicStrategy","authMethod","cache","userTest","encodedAuth","context","beforeAll","module","Test","createTestingModule","providers","AuthBasicStrategy","provide","AuthMethod","useValue","validateUser","PinoLogger","assign","undefined","error","jest","fn","Cache","get","_key","set","_value","_ttl","genSlugKey","compile","UserModel","generateUserTest","Buffer","from","login","password","toString","createMock","it","expect","toBeDefined","mockReturnValueOnce","switchToHttp","getRequest","mockReturnValue","raw","user","headers","authorization","canActivate","toBe","toBeUndefined","passwordWithColon","userWithColonPassword","encodedAuthWithColon","mockImplementation","rejects","toThrow","spyOn","mockRejectedValueOnce","Error","loggerSpy","toHaveBeenCalled","mock","calls","toEqual","stringContaining","AUTHORIZATION","WEBDAV_BASE_PATH","url","method","originalUrl"],"mappings":"AAAA;;;;CAIC;;;;wBAEsC;yBAEH;4BACT;2BACD;sBACO;wBACA;8BACX;4BACK;gCACI;mCACG;AAElCA,SAASC,8BAAc,CAACC,IAAI,EAAE;IAC5B,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAEJC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACTb,8BAAc;gBACdc,oCAAiB;gBACjB;oBACEC,SAASC,sBAAU;oBACnBC,UAAU;wBACRC,cAAc,UAAY;oBAC5B;gBACF;gBACA;oBACEH,SAASI,sBAAU;oBACnBF,UAAU;wBACRG,QAAQ,IAAMC;wBACdC,OAAOC,KAAKC,EAAE;oBAChB;gBACF;gBACA;oBACET,SAASU,mBAAK;oBACdR,UAAU;wBACRS,KAAK,CAACC,OAAiBN;wBACvBO,KAAK,OAAOD,MAAcE,QAAgBC,OAAiBT;wBAC3DU,YAAY,IAAM;oBACpB;gBACF;aACD;QACH,GAAGC,OAAO;QAEV9B,iBAAiBQ,OAAOgB,GAAG,CAAiB1B,8BAAc;QAC1DG,oBAAoBO,OAAOgB,GAAG,CAAoBZ,oCAAiB;QACnEV,aAAaM,OAAOgB,GAAG,CAAaV,sBAAU;QAC9CX,QAAQK,OAAOgB,GAAG,CAAQD,mBAAK;QAC/BnB,WAAW,IAAI2B,oBAAS,CAACC,IAAAA,sBAAgB,KAAI;QAC7C3B,cAAc4B,OAAOC,IAAI,CAAC,GAAG9B,SAAS+B,KAAK,CAAC,CAAC,EAAE/B,SAASgC,QAAQ,EAAE,EAAEC,QAAQ,CAAC;QAC7E/B,UAAUgC,IAAAA,kBAAU;IACtB;IAEAC,GAAG,qBAAqB;QACtBC,OAAOxC,gBAAgByC,WAAW;QAClCD,OAAOvC,mBAAmBwC,WAAW;QACrCD,OAAOtC,YAAYuC,WAAW;QAC9BD,OAAOrC,OAAOsC,WAAW;QACzBD,OAAOnC,aAAaoC,WAAW;QAC/BD,OAAOpC,UAAUqC,WAAW;QAC5BD,OAAOpC,SAASgC,QAAQ,EAAEK,WAAW;IACvC;IAEAF,GAAG,2CAA2C;QAC5CrC,WAAWc,YAAY,GAAGK,KAAKC,EAAE,GAAGoB,mBAAmB,CAACtC;QACxDE,QAAQqC,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEC,eAAe,CAAC,MAAM,EAAE5C,aAAa;YAAC;QACnD;QACAmC,OAAO,MAAMxC,eAAekD,WAAW,CAAC5C,UAAU6C,IAAI,CAAC;QACvDX,OAAOpC,SAASgC,QAAQ,EAAEgB,aAAa;IACzC;IAEAb,GAAG,0EAA0E;QAC3E,MAAMc,oBAAoB;QAC1B,MAAMC,wBAAwB,IAAIvB,oBAAS,CAAC;YAAE,GAAGC,IAAAA,sBAAgB,GAAE;YAAEI,UAAUiB;QAAkB,GAAG;QACpG,MAAME,uBAAuBtB,OAAOC,IAAI,CAAC,GAAGoB,sBAAsBnB,KAAK,CAAC,CAAC,EAAEkB,mBAAmB,EAAEhB,QAAQ,CAAC;QAEzGnC,WAAWc,YAAY,GAAGK,KAAKC,EAAE,GAAGkC,kBAAkB,CAAC,CAACrB,OAAeC;YACrEI,OAAOL,OAAOgB,IAAI,CAACG,sBAAsBnB,KAAK;YAC9CK,OAAOJ,UAAUe,IAAI,CAACE;YACtB,OAAOC;QACT;QACAhD,QAAQqC,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEC,eAAe,CAAC,MAAM,EAAEM,sBAAsB;YAAC;QAC5D;QACAf,OAAO,MAAMxC,eAAekD,WAAW,CAAC5C,UAAU6C,IAAI,CAAC;QACvDX,OAAOc,sBAAsBlB,QAAQ,EAAEgB,aAAa;IACtD;IAEAb,GAAG,sDAAsD;QACvDpC,MAAMqB,GAAG,GAAGH,KAAKC,EAAE,GAAGoB,mBAAmB,CAACtC;QAC1CE,QAAQqC,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEC,eAAe,CAAC,MAAM,EAAE5C,aAAa;YAAC;QACnD;QACAmC,OAAO,MAAMxC,eAAekD,WAAW,CAAC5C,UAAU6C,IAAI,CAAC;IACzD;IAEAZ,GAAG,iGAAiG;QAClGpC,MAAMqB,GAAG,GAAGH,KAAKC,EAAE,GAAGoB,mBAAmB,CAAC;QAC1CpC,QAAQqC,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEC,eAAe,CAAC,MAAM,EAAE5C,aAAa;YAAC;QACnD;QACA,MAAMmC,OAAOxC,eAAekD,WAAW,CAAC5C,UAAUmD,OAAO,CAACC,OAAO;IACnE;IAEAnB,GAAG,qGAAqG;QACtGpC,MAAMqB,GAAG,GAAGH,KAAKC,EAAE,GAAGoB,mBAAmB,CAACvB;QAC1CjB,WAAWc,YAAY,GAAGK,KAAKC,EAAE,GAAGoB,mBAAmB,CAAC;QACxDrB,KAAKsC,KAAK,CAACxD,OAAO,OAAOyD,qBAAqB,CAAC,IAAIC,MAAM;QACzDvD,QAAQqC,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEC,eAAe,CAAC,MAAM,EAAE5C,aAAa;YAAC;QACnD;QACA,MAAMyD,YAAYzC,KACfsC,KAAK,CAAC1D,iBAAiB,CAAC,SAAS,EAAE,SAAS,8CAA8C;SAC1FuD,kBAAkB,CAAC,IAAMrC;QAC5B,MAAMqB,OAAOxC,eAAekD,WAAW,CAAC5C,UAAUmD,OAAO,CAACC,OAAO;QACjElB,OAAOsB,WAAWC,gBAAgB;QAClCvB,OAAOsB,UAAUE,IAAI,CAACC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAEC,OAAO,CAAC1B,OAAO2B,gBAAgB,CAAC;IACrE;IAEA5B,GAAG,+CAA+C;QAChDjC,QAAQqC,YAAY,GAAGC,UAAU,CAACC,eAAe,CAAC;YAChDC,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEC,eAAe,CAAC,MAAM,EAAE5C,aAAa;YAAC;QACnD;QACA,MAAMmC,OAAOxC,eAAekD,WAAW,CAAC5C,UAAUmD,OAAO,CAACC,OAAO;IACnE;IAEAnB,GAAG,4DAA4D;QAC7D,oCAAoC;QACpCjC,QAAQqC,YAAY,GAAGC,UAAU,CAACF,mBAAmB,CAAC;YACpDI,KAAK;gBAAEC,MAAM;YAAG;YAChBC,SAAS;gBAAEoB,eAAe;YAAY;QACxC;QACA,MAAM5B,OAAOxC,eAAekD,WAAW,CAAC5C,UAAUmD,OAAO,CAACC,OAAO;QACjEpD,QAAQqC,YAAY,GAAGC,UAAU,CAACF,mBAAmB,CAAC;YACpDI,KAAK;gBAAEC,MAAM;YAAG;QAClB;QACA,MAAMP,OAAOxC,eAAekD,WAAW,CAAC5C,UAAUmD,OAAO,CAACC,OAAO;IACnE;IAEAnB,GAAG,CAAC,uEAAuE,EAAE8B,wBAAgB,CAAC,UAAU,CAAC,EAAE;QACzG,KAAK,MAAMC,OAAO;YAAC;YAAI,CAAC,CAAC,EAAED,wBAAgB,EAAE;YAAE,CAAC,CAAC,EAAEA,wBAAgB,CAAC,QAAQ,CAAC;SAAC,CAAE;YAC9E/D,QAAQqC,YAAY,GAAGC,UAAU,CAACF,mBAAmB,CAAC;gBACpD6B,QAAQ;gBACRC,aAAaF;gBACbxB,KAAK;oBAAEC,MAAM;gBAAG;YAClB;YACAP,OAAO,MAAMxC,eAAekD,WAAW,CAAC5C,UAAU6C,IAAI,CAAC;QACzD;IACF;IAEAZ,GAAG,oDAAoD;QACrDjC,QAAQqC,YAAY,GAAGC,UAAU,CAACF,mBAAmB,CAAC;YACpD6B,QAAQ;YACRC,aAAa;YACb1B,KAAK;gBAAEC,MAAM;YAAG;QAClB;QACA,MAAMP,OAAOxC,eAAekD,WAAW,CAAC5C,UAAUmD,OAAO,CAACC,OAAO;IACnE;AACF"}
|
|
@@ -16,12 +16,12 @@ const _common = require("@nestjs/common");
|
|
|
16
16
|
const _passport = require("@nestjs/passport");
|
|
17
17
|
const _classtransformer = require("class-transformer");
|
|
18
18
|
const _nestjspino = require("nestjs-pino");
|
|
19
|
-
const _passporthttp = require("passport-http");
|
|
20
19
|
const _usermodel = require("../../applications/users/models/user.model");
|
|
21
20
|
const _shared = require("../../common/shared");
|
|
22
21
|
const _cacheservice = require("../../infrastructure/cache/services/cache.service");
|
|
23
22
|
const _scope = require("../constants/scope");
|
|
24
23
|
const _authmethod = require("../models/auth-method");
|
|
24
|
+
const _httpbasicstrategy = require("./implementations/http-basic.strategy");
|
|
25
25
|
function _ts_decorate(decorators, target, key, desc) {
|
|
26
26
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
27
27
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -31,7 +31,7 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
31
31
|
function _ts_metadata(k, v) {
|
|
32
32
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
33
33
|
}
|
|
34
|
-
let AuthBasicStrategy = class AuthBasicStrategy extends (0, _passport.PassportStrategy)(
|
|
34
|
+
let AuthBasicStrategy = class AuthBasicStrategy extends (0, _passport.PassportStrategy)(_httpbasicstrategy.HttpBasicStrategy, 'basic') {
|
|
35
35
|
async validate(req, loginOrEmail, password) {
|
|
36
36
|
loginOrEmail = loginOrEmail.trim();
|
|
37
37
|
password = password.trim();
|
|
@@ -46,7 +46,7 @@ let AuthBasicStrategy = class AuthBasicStrategy extends (0, _passport.PassportSt
|
|
|
46
46
|
}
|
|
47
47
|
if (userFromCache !== undefined) {
|
|
48
48
|
// cached
|
|
49
|
-
// warning: plainToInstance do not use constructor to instantiate class
|
|
49
|
+
// warning: plainToInstance do not use constructor to instantiate the class
|
|
50
50
|
return (0, _classtransformer.plainToInstance)(_usermodel.UserModel, userFromCache);
|
|
51
51
|
}
|
|
52
52
|
const userFromDB = await this.authMethod.validateUser(loginOrEmail, password, req.ip, _scope.AUTH_SCOPE.WEBDAV);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { AbstractStrategy, PassportStrategy } from '@nestjs/passport'\nimport { instanceToPlain, plainToInstance } from 'class-transformer'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { AbstractStrategy, PassportStrategy } from '@nestjs/passport'\nimport { instanceToPlain, plainToInstance } from 'class-transformer'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { SERVER_NAME } from '../../common/shared'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AUTH_SCOPE } from '../constants/scope'\nimport { AuthMethod } from '../models/auth-method'\nimport { HttpBasicStrategy } from './implementations/http-basic.strategy'\n\n@Injectable()\nexport class AuthBasicStrategy extends PassportStrategy(HttpBasicStrategy, 'basic') implements AbstractStrategy {\n private readonly CACHE_TTL = 900\n private readonly CACHE_KEY_PREFIX = 'auth-webdav'\n\n constructor(\n private readonly authMethod: AuthMethod,\n private readonly cache: Cache,\n private readonly logger: PinoLogger\n ) {\n super({ passReqToCallback: true, realm: SERVER_NAME })\n }\n\n async validate(req: FastifyRequest, loginOrEmail: string, password: string): Promise<Omit<UserModel, 'password'> | null> {\n loginOrEmail = loginOrEmail.trim()\n password = password.trim()\n this.logger.assign({ user: loginOrEmail })\n const authBasicUser = `${this.CACHE_KEY_PREFIX}-${req.headers['authorization'].split(' ').at(-1).toLowerCase()}`\n const userFromCache: any = await this.cache.get(authBasicUser)\n if (userFromCache === null) {\n // not authorized\n return null\n }\n if (userFromCache !== undefined) {\n // cached\n // warning: plainToInstance do not use constructor to instantiate the class\n return plainToInstance(UserModel, userFromCache)\n }\n const userFromDB: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip, AUTH_SCOPE.WEBDAV)\n if (userFromDB !== null) {\n userFromDB.removePassword()\n }\n const userToCache: Record<string, any> | null = userFromDB ? instanceToPlain(userFromDB, { excludePrefixes: ['_'] }) : null\n this.cache.set(authBasicUser, userToCache, this.CACHE_TTL).catch((e: Error) => this.logger.error(`${this.validate.name} - ${e}`))\n return userFromDB\n }\n}\n"],"names":["AuthBasicStrategy","PassportStrategy","HttpBasicStrategy","validate","req","loginOrEmail","password","trim","logger","assign","user","authBasicUser","CACHE_KEY_PREFIX","headers","split","at","toLowerCase","userFromCache","cache","get","undefined","plainToInstance","UserModel","userFromDB","authMethod","validateUser","ip","AUTH_SCOPE","WEBDAV","removePassword","userToCache","instanceToPlain","excludePrefixes","set","CACHE_TTL","catch","e","error","name","passReqToCallback","realm","SERVER_NAME"],"mappings":"AAAA;;;;CAIC;;;;+BAeYA;;;eAAAA;;;wBAbc;0BACwB;kCACF;4BAEtB;2BACD;wBACE;8BACN;uBACK;4BACA;mCACO;;;;;;;;;;AAG3B,IAAA,AAAMA,oBAAN,MAAMA,0BAA0BC,IAAAA,0BAAgB,EAACC,oCAAiB,EAAE;IAYzE,MAAMC,SAASC,GAAmB,EAAEC,YAAoB,EAAEC,QAAgB,EAA+C;QACvHD,eAAeA,aAAaE,IAAI;QAChCD,WAAWA,SAASC,IAAI;QACxB,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAML;QAAa;QACxC,MAAMM,gBAAgB,GAAG,IAAI,CAACC,gBAAgB,CAAC,CAAC,EAAER,IAAIS,OAAO,CAAC,gBAAgB,CAACC,KAAK,CAAC,KAAKC,EAAE,CAAC,CAAC,GAAGC,WAAW,IAAI;QAChH,MAAMC,gBAAqB,MAAM,IAAI,CAACC,KAAK,CAACC,GAAG,CAACR;QAChD,IAAIM,kBAAkB,MAAM;YAC1B,iBAAiB;YACjB,OAAO;QACT;QACA,IAAIA,kBAAkBG,WAAW;YAC/B,SAAS;YACT,2EAA2E;YAC3E,OAAOC,IAAAA,iCAAe,EAACC,oBAAS,EAAEL;QACpC;QACA,MAAMM,aAAwB,MAAM,IAAI,CAACC,UAAU,CAACC,YAAY,CAACpB,cAAcC,UAAUF,IAAIsB,EAAE,EAAEC,iBAAU,CAACC,MAAM;QAClH,IAAIL,eAAe,MAAM;YACvBA,WAAWM,cAAc;QAC3B;QACA,MAAMC,cAA0CP,aAAaQ,IAAAA,iCAAe,EAACR,YAAY;YAAES,iBAAiB;gBAAC;aAAI;QAAC,KAAK;QACvH,IAAI,CAACd,KAAK,CAACe,GAAG,CAACtB,eAAemB,aAAa,IAAI,CAACI,SAAS,EAAEC,KAAK,CAAC,CAACC,IAAa,IAAI,CAAC5B,MAAM,CAAC6B,KAAK,CAAC,GAAG,IAAI,CAAClC,QAAQ,CAACmC,IAAI,CAAC,GAAG,EAAEF,GAAG;QAC/H,OAAOb;IACT;IA9BA,YACE,AAAiBC,UAAsB,EACvC,AAAiBN,KAAY,EAC7B,AAAiBV,MAAkB,CACnC;QACA,KAAK,CAAC;YAAE+B,mBAAmB;YAAMC,OAAOC,mBAAW;QAAC,SAJnCjB,aAAAA,iBACAN,QAAAA,YACAV,SAAAA,aANF0B,YAAY,UACZtB,mBAAmB;IAQpC;AAyBF"}
|
|
@@ -5,22 +5,43 @@
|
|
|
5
5
|
*/ // import { Injectable } from '@nestjs/common'
|
|
6
6
|
// import { PassportStrategy } from '@nestjs/passport'
|
|
7
7
|
// import { PinoLogger } from 'nestjs-pino'
|
|
8
|
-
// import {
|
|
9
|
-
//
|
|
10
|
-
// import {
|
|
8
|
+
// import { SERVER_NAME } from '../../common/shared'
|
|
9
|
+
//
|
|
10
|
+
// import { HttpDigestStrategy } from './implementations/http-digest.strategy'
|
|
11
11
|
//
|
|
12
12
|
// @Injectable()
|
|
13
|
-
// export class AuthDigestStrategy extends PassportStrategy(
|
|
14
|
-
// constructor(
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
13
|
+
// export class AuthDigestStrategy extends PassportStrategy(HttpDigestStrategy, 'digest') {
|
|
14
|
+
// constructor(private readonly logger: PinoLogger) {
|
|
15
|
+
// super({
|
|
16
|
+
// realm: SERVER_NAME,
|
|
17
|
+
// // Recommended options for RFC-compliant Digest (required for security)
|
|
18
|
+
// qop: 'auth',
|
|
19
|
+
// algorithm: 'MD5'
|
|
20
|
+
// // Optional anti-replay validation hook
|
|
21
|
+
// // validate: (params, done) => done(null, true),
|
|
22
|
+
// })
|
|
19
23
|
// }
|
|
20
24
|
//
|
|
21
|
-
// validate(loginOrEmail: string) {
|
|
25
|
+
// async validate(loginOrEmail: string) {
|
|
26
|
+
// loginOrEmail = loginOrEmail.trim()
|
|
27
|
+
// this.logger.assign({ user: loginOrEmail })
|
|
28
|
+
//
|
|
29
|
+
// // ⚠️ TO ADAPT: Digest authentication requires a server-side secret:
|
|
30
|
+
// // - ideally a stored { ha1 } value (HA1 = MD5(username:realm:password))
|
|
31
|
+
// // - otherwise the clear-text "password" (less secure, but possible)
|
|
32
|
+
// //
|
|
22
33
|
// // return [loginOrEmail, { ha1: '4befe40c6af915eca11de84be07a1f21' }]
|
|
23
|
-
// return [loginOrEmail, 'password']
|
|
34
|
+
// // return [loginOrEmail, 'password']
|
|
35
|
+
//
|
|
36
|
+
// // Method to get digest secret
|
|
37
|
+
// const { user, ha1, password } = getDigestSecret(loginOrEmail, SERVER_NAME)
|
|
38
|
+
//
|
|
39
|
+
// if (!user) return null
|
|
40
|
+
//
|
|
41
|
+
// if (ha1) return [user, { ha1 }]
|
|
42
|
+
// if (password) return [user, password]
|
|
43
|
+
//
|
|
44
|
+
// return null
|
|
24
45
|
// }
|
|
25
46
|
// }
|
|
26
47
|
"use strict";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-digest.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\n// import { Injectable } from '@nestjs/common'\n// import { PassportStrategy } from '@nestjs/passport'\n// import { PinoLogger } from 'nestjs-pino'\n// import {
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-digest.strategy.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\n// import { Injectable } from '@nestjs/common'\n// import { PassportStrategy } from '@nestjs/passport'\n// import { PinoLogger } from 'nestjs-pino'\n// import { SERVER_NAME } from '../../common/shared'\n//\n// import { HttpDigestStrategy } from './implementations/http-digest.strategy'\n//\n// @Injectable()\n// export class AuthDigestStrategy extends PassportStrategy(HttpDigestStrategy, 'digest') {\n// constructor(private readonly logger: PinoLogger) {\n// super({\n// realm: SERVER_NAME,\n// // Recommended options for RFC-compliant Digest (required for security)\n// qop: 'auth',\n// algorithm: 'MD5'\n// // Optional anti-replay validation hook\n// // validate: (params, done) => done(null, true),\n// })\n// }\n//\n// async validate(loginOrEmail: string) {\n// loginOrEmail = loginOrEmail.trim()\n// this.logger.assign({ user: loginOrEmail })\n//\n// // ⚠️ TO ADAPT: Digest authentication requires a server-side secret:\n// // - ideally a stored { ha1 } value (HA1 = MD5(username:realm:password))\n// // - otherwise the clear-text \"password\" (less secure, but possible)\n// //\n// // return [loginOrEmail, { ha1: '4befe40c6af915eca11de84be07a1f21' }]\n// // return [loginOrEmail, 'password']\n//\n// // Method to get digest secret\n// const { user, ha1, password } = getDigestSecret(loginOrEmail, SERVER_NAME)\n//\n// if (!user) return null\n//\n// if (ha1) return [user, { ha1 }]\n// if (password) return [user, password]\n//\n// return null\n// }\n// }\n"],"names":[],"mappings":"AAAA;;;;CAIC,GAED,8CAA8C;AAC9C,sDAAsD;AACtD,2CAA2C;AAC3C,oDAAoD;AACpD,EAAE;AACF,8EAA8E;AAC9E,EAAE;AACF,gBAAgB;AAChB,2FAA2F;AAC3F,uDAAuD;AACvD,cAAc;AACd,4BAA4B;AAC5B,gFAAgF;AAChF,qBAAqB;AACrB,yBAAyB;AACzB,gDAAgD;AAChD,yDAAyD;AACzD,SAAS;AACT,MAAM;AACN,EAAE;AACF,2CAA2C;AAC3C,yCAAyC;AACzC,iDAAiD;AACjD,EAAE;AACF,2EAA2E;AAC3E,+EAA+E;AAC/E,2EAA2E;AAC3E,SAAS;AACT,4EAA4E;AAC5E,2CAA2C;AAC3C,EAAE;AACF,qCAAqC;AACrC,iFAAiF;AACjF,EAAE;AACF,6BAA6B;AAC7B,EAAE;AACF,sCAAsC;AACtC,4CAA4C;AAC5C,EAAE;AACF,kBAAkB;AAClB,MAAM;AACN,IAAI"}
|