@3sixty/api-auth-entity-storage-service 0.0.0-stage → 0.20.0-next.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.
Files changed (96) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +24 -2
  3. package/dist/es/entities/authenticationUser.js +61 -0
  4. package/dist/es/entities/authenticationUser.js.map +1 -0
  5. package/dist/es/entities/authenticationUserV0.js +69 -0
  6. package/dist/es/entities/authenticationUserV0.js.map +1 -0
  7. package/dist/es/index.js +21 -0
  8. package/dist/es/index.js.map +1 -0
  9. package/dist/es/models/IAuthHeaderProcessorConfig.js +4 -0
  10. package/dist/es/models/IAuthHeaderProcessorConfig.js.map +1 -0
  11. package/dist/es/models/IAuthHeaderProcessorConstructorOptions.js +2 -0
  12. package/dist/es/models/IAuthHeaderProcessorConstructorOptions.js.map +1 -0
  13. package/dist/es/models/IAuthTokenContext.js +4 -0
  14. package/dist/es/models/IAuthTokenContext.js.map +1 -0
  15. package/dist/es/models/IEntityStorageAuthenticationAdminServiceConfig.js +4 -0
  16. package/dist/es/models/IEntityStorageAuthenticationAdminServiceConfig.js.map +1 -0
  17. package/dist/es/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js +2 -0
  18. package/dist/es/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js.map +1 -0
  19. package/dist/es/models/IEntityStorageAuthenticationServiceConfig.js +2 -0
  20. package/dist/es/models/IEntityStorageAuthenticationServiceConfig.js.map +1 -0
  21. package/dist/es/models/IEntityStorageAuthenticationServiceConstructorOptions.js +2 -0
  22. package/dist/es/models/IEntityStorageAuthenticationServiceConstructorOptions.js.map +1 -0
  23. package/dist/es/processors/authHeaderProcessor.js +272 -0
  24. package/dist/es/processors/authHeaderProcessor.js.map +1 -0
  25. package/dist/es/restEntryPoints.js +20 -0
  26. package/dist/es/restEntryPoints.js.map +1 -0
  27. package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js +375 -0
  28. package/dist/es/routes/entityStorageAuthenticationAdminRoutes.js.map +1 -0
  29. package/dist/es/routes/entityStorageAuthenticationRoutes.js +291 -0
  30. package/dist/es/routes/entityStorageAuthenticationRoutes.js.map +1 -0
  31. package/dist/es/schema.js +13 -0
  32. package/dist/es/schema.js.map +1 -0
  33. package/dist/es/services/entityStorageAuthenticationAdminService.js +324 -0
  34. package/dist/es/services/entityStorageAuthenticationAdminService.js.map +1 -0
  35. package/dist/es/services/entityStorageAuthenticationService.js +338 -0
  36. package/dist/es/services/entityStorageAuthenticationService.js.map +1 -0
  37. package/dist/es/utils/passwordHelper.js +60 -0
  38. package/dist/es/utils/passwordHelper.js.map +1 -0
  39. package/dist/es/utils/tokenHelper.js +121 -0
  40. package/dist/es/utils/tokenHelper.js.map +1 -0
  41. package/dist/types/entities/authenticationUser.d.ts +29 -0
  42. package/dist/types/entities/authenticationUserV0.d.ts +33 -0
  43. package/dist/types/index.d.ts +18 -0
  44. package/dist/types/models/IAuthHeaderProcessorConfig.d.ts +38 -0
  45. package/dist/types/models/IAuthHeaderProcessorConstructorOptions.d.ts +25 -0
  46. package/dist/types/models/IAuthTokenContext.d.ts +30 -0
  47. package/dist/types/models/IEntityStorageAuthenticationAdminServiceConfig.d.ts +25 -0
  48. package/dist/types/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.d.ts +25 -0
  49. package/dist/types/models/IEntityStorageAuthenticationServiceConfig.d.ts +41 -0
  50. package/dist/types/models/IEntityStorageAuthenticationServiceConstructorOptions.d.ts +35 -0
  51. package/dist/types/processors/authHeaderProcessor.d.ts +57 -0
  52. package/dist/types/restEntryPoints.d.ts +5 -0
  53. package/dist/types/routes/entityStorageAuthenticationAdminRoutes.d.ts +62 -0
  54. package/dist/types/routes/entityStorageAuthenticationRoutes.d.ts +53 -0
  55. package/dist/types/schema.d.ts +4 -0
  56. package/dist/types/services/entityStorageAuthenticationAdminService.d.ts +69 -0
  57. package/dist/types/services/entityStorageAuthenticationService.d.ts +70 -0
  58. package/dist/types/utils/passwordHelper.d.ts +27 -0
  59. package/dist/types/utils/tokenHelper.d.ts +51 -0
  60. package/docs/changelog.md +19 -0
  61. package/docs/examples.md +94 -0
  62. package/docs/open-api/spec.json +1344 -0
  63. package/docs/reference/classes/AuthHeaderProcessor.md +197 -0
  64. package/docs/reference/classes/AuthenticationUser.md +61 -0
  65. package/docs/reference/classes/AuthenticationUserV0.md +69 -0
  66. package/docs/reference/classes/EntityStorageAuthenticationAdminService.md +237 -0
  67. package/docs/reference/classes/EntityStorageAuthenticationService.md +239 -0
  68. package/docs/reference/classes/PasswordHelper.md +74 -0
  69. package/docs/reference/classes/TokenHelper.md +163 -0
  70. package/docs/reference/functions/authenticationAdminCreateUser.md +37 -0
  71. package/docs/reference/functions/authenticationAdminGetUser.md +31 -0
  72. package/docs/reference/functions/authenticationAdminGetUserByIdentity.md +31 -0
  73. package/docs/reference/functions/authenticationAdminRemoveUser.md +31 -0
  74. package/docs/reference/functions/authenticationAdminUpdateUser.md +31 -0
  75. package/docs/reference/functions/authenticationAdminUpdateUserPassword.md +31 -0
  76. package/docs/reference/functions/authenticationLogin.md +31 -0
  77. package/docs/reference/functions/authenticationLogout.md +31 -0
  78. package/docs/reference/functions/authenticationPasswordPolicy.md +31 -0
  79. package/docs/reference/functions/authenticationRefreshToken.md +31 -0
  80. package/docs/reference/functions/authenticationUpdatePassword.md +31 -0
  81. package/docs/reference/functions/generateRestRoutesAuthentication.md +25 -0
  82. package/docs/reference/functions/generateRestRoutesAuthenticationAdmin.md +25 -0
  83. package/docs/reference/functions/initSchema.md +9 -0
  84. package/docs/reference/index.md +44 -0
  85. package/docs/reference/interfaces/IAuthHeaderProcessorConfig.md +85 -0
  86. package/docs/reference/interfaces/IAuthHeaderProcessorConstructorOptions.md +53 -0
  87. package/docs/reference/interfaces/IAuthTokenContext.md +52 -0
  88. package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConfig.md +59 -0
  89. package/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md +53 -0
  90. package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConfig.md +101 -0
  91. package/docs/reference/interfaces/IEntityStorageAuthenticationServiceConstructorOptions.md +81 -0
  92. package/docs/reference/variables/restEntryPoints.md +5 -0
  93. package/docs/reference/variables/tagsAuthentication.md +5 -0
  94. package/docs/reference/variables/tagsAuthenticationAdmin.md +5 -0
  95. package/locales/en.json +37 -0
  96. package/package.json +63 -5
@@ -0,0 +1,291 @@
1
+ import { ComponentFactory, Guards } from "@3sixty/core";
2
+ import { HttpStatusCode } from "@3sixty/web";
3
+ /**
4
+ * The source used when communicating about these routes.
5
+ */
6
+ const ROUTES_SOURCE = "authenticationRoutes";
7
+ /**
8
+ * The tag to associate with the routes.
9
+ */
10
+ export const tagsAuthentication = [
11
+ {
12
+ name: "Authentication",
13
+ description: "Authentication endpoints for the REST server."
14
+ }
15
+ ];
16
+ /**
17
+ * The REST routes for authentication.
18
+ * @param baseRouteName Prefix to prepend to the paths.
19
+ * @param componentName The name of the component to use in the routes stored in the ComponentFactory.
20
+ * @returns The generated routes.
21
+ */
22
+ export function generateRestRoutesAuthentication(baseRouteName, componentName) {
23
+ const loginRoute = {
24
+ operationId: "authenticationLogin",
25
+ summary: "Login to the server",
26
+ tag: tagsAuthentication[0].name,
27
+ method: "POST",
28
+ path: `${baseRouteName}/login`,
29
+ handler: async (httpRequestContext, request) => authenticationLogin(httpRequestContext, componentName, request),
30
+ requestType: {
31
+ type: "ILoginRequest",
32
+ examples: [
33
+ {
34
+ id: "loginRequestExample",
35
+ description: "The request to login to the server.",
36
+ request: {
37
+ body: {
38
+ email: "user@example.com",
39
+ password: "MyPassword123!"
40
+ }
41
+ }
42
+ }
43
+ ]
44
+ },
45
+ responseType: [
46
+ {
47
+ type: "ILoginResponse",
48
+ examples: [
49
+ {
50
+ id: "loginResponseExample",
51
+ description: "The response for the login request.",
52
+ response: {
53
+ body: {
54
+ expiry: 1722514341067
55
+ }
56
+ }
57
+ }
58
+ ]
59
+ },
60
+ {
61
+ type: "IUnauthorizedResponse"
62
+ }
63
+ ],
64
+ skipAuth: true
65
+ };
66
+ const logoutRoute = {
67
+ operationId: "authenticationLogout",
68
+ summary: "Logout from the server",
69
+ tag: tagsAuthentication[0].name,
70
+ method: "POST",
71
+ path: `${baseRouteName}/logout`,
72
+ handler: async (httpRequestContext, request) => authenticationLogout(httpRequestContext, componentName, request),
73
+ requestType: {
74
+ type: "ILogoutRequest",
75
+ examples: [
76
+ {
77
+ id: "logoutRequestExample",
78
+ description: "The request to logout from the server.",
79
+ request: {
80
+ body: {
81
+ token: "eyJhbGciOiJIU...sw5c"
82
+ }
83
+ }
84
+ }
85
+ ]
86
+ },
87
+ responseType: [
88
+ {
89
+ type: "INoContentResponse"
90
+ }
91
+ ]
92
+ };
93
+ const refreshTokenRoute = {
94
+ operationId: "authenticationRefreshToken",
95
+ summary: "Refresh an authentication token",
96
+ tag: tagsAuthentication[0].name,
97
+ method: "POST",
98
+ path: `${baseRouteName}/refresh`,
99
+ handler: async (httpRequestContext, request) => authenticationRefreshToken(httpRequestContext, componentName, request),
100
+ requestType: {
101
+ type: "IRefreshTokenRequest",
102
+ examples: [
103
+ {
104
+ id: "refreshTokenRequestExample",
105
+ description: "The request to refresh an auth token.",
106
+ request: {
107
+ body: {
108
+ token: "eyJhbGciOiJIU...sw5c"
109
+ }
110
+ }
111
+ }
112
+ ]
113
+ },
114
+ responseType: [
115
+ {
116
+ type: "IRefreshTokenResponse",
117
+ examples: [
118
+ {
119
+ id: "refreshTokenResponseExample",
120
+ description: "The response for the refresh token request.",
121
+ response: {
122
+ body: {
123
+ expiry: 1722514341067
124
+ }
125
+ }
126
+ }
127
+ ]
128
+ },
129
+ {
130
+ type: "IUnauthorizedResponse"
131
+ }
132
+ ]
133
+ };
134
+ const updatePasswordRoute = {
135
+ operationId: "authenticationUpdatePassword",
136
+ summary: "Update the current user's password",
137
+ tag: tagsAuthentication[0].name,
138
+ method: "PUT",
139
+ path: `${baseRouteName}/password`,
140
+ handler: async (httpRequestContext, request) => authenticationUpdatePassword(httpRequestContext, componentName, request),
141
+ requestType: {
142
+ type: "IUpdatePasswordRequest",
143
+ examples: [
144
+ {
145
+ id: "updatePasswordRequestExample",
146
+ description: "The request to update the current user's password.",
147
+ request: {
148
+ body: {
149
+ currentPassword: "MyNewPassword123!",
150
+ newPassword: "MyNewPassword123!"
151
+ }
152
+ }
153
+ }
154
+ ]
155
+ },
156
+ responseType: [
157
+ {
158
+ type: "INoContentResponse"
159
+ },
160
+ {
161
+ type: "IUnauthorizedResponse"
162
+ }
163
+ ]
164
+ };
165
+ const passwordPolicyRoute = {
166
+ operationId: "authenticationPasswordPolicy",
167
+ summary: "Get the password policy",
168
+ tag: tagsAuthentication[0].name,
169
+ method: "GET",
170
+ path: `${baseRouteName}/policy`,
171
+ handler: async (httpRequestContext, request) => authenticationPasswordPolicy(httpRequestContext, componentName, request),
172
+ requestType: {
173
+ type: "INoContentRequest"
174
+ },
175
+ responseType: [
176
+ {
177
+ type: "IPasswordPolicyResponse",
178
+ examples: [
179
+ {
180
+ id: "passwordPolicyResponseExample",
181
+ description: "The response for the password policy request.",
182
+ response: {
183
+ body: {
184
+ minPasswordLength: 15,
185
+ maxPasswordLength: 128
186
+ }
187
+ }
188
+ }
189
+ ]
190
+ }
191
+ ],
192
+ skipAuth: true,
193
+ skipTenant: true
194
+ };
195
+ return [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute, passwordPolicyRoute];
196
+ }
197
+ /**
198
+ * Login to the server.
199
+ * @param httpRequestContext The request context for the API.
200
+ * @param componentName The name of the component to use in the routes.
201
+ * @param request The request.
202
+ * @returns The response object with additional http response properties.
203
+ */
204
+ export async function authenticationLogin(httpRequestContext, componentName, request) {
205
+ Guards.object(ROUTES_SOURCE, "request", request);
206
+ Guards.object(ROUTES_SOURCE, "request.body", request.body);
207
+ const component = ComponentFactory.get(componentName);
208
+ const result = await component.login(request.body.email, request.body.password);
209
+ // Need to give a hint to any auth processors about the operation
210
+ // in case they need to manipulate the response
211
+ httpRequestContext.processorState.authOperation = "login";
212
+ httpRequestContext.processorState.authToken = result.token;
213
+ return {
214
+ body: {
215
+ expiry: result.expiry
216
+ }
217
+ };
218
+ }
219
+ /**
220
+ * Logout from the server.
221
+ * @param httpRequestContext The request context for the API.
222
+ * @param componentName The name of the component to use in the routes.
223
+ * @param request The request.
224
+ * @returns The response object with additional http response properties.
225
+ */
226
+ export async function authenticationLogout(httpRequestContext, componentName, request) {
227
+ Guards.object(ROUTES_SOURCE, "request", request);
228
+ const component = ComponentFactory.get(componentName);
229
+ await component.logout(request.body?.token);
230
+ // Need to give a hint to any auth processors about the operation
231
+ // in case they need to manipulate the response
232
+ httpRequestContext.processorState.authOperation = "logout";
233
+ return {
234
+ statusCode: HttpStatusCode.noContent
235
+ };
236
+ }
237
+ /**
238
+ * Refresh the login token.
239
+ * @param httpRequestContext The request context for the API.
240
+ * @param componentName The name of the component to use in the routes.
241
+ * @param request The request.
242
+ * @returns The response object with additional http response properties.
243
+ */
244
+ export async function authenticationRefreshToken(httpRequestContext, componentName, request) {
245
+ Guards.object(ROUTES_SOURCE, "request", request);
246
+ const component = ComponentFactory.get(componentName);
247
+ // If the token is not in the body, then maybe an auth processor has extracted it
248
+ // and stored it in the processor state
249
+ const token = request.body?.token ?? httpRequestContext.processorState.authToken;
250
+ const result = await component.refresh(token);
251
+ // Need to give a hint to any auth processors about the operation
252
+ // in case they need to manipulate the response
253
+ httpRequestContext.processorState.authOperation = "refresh";
254
+ httpRequestContext.processorState.authToken = result.token;
255
+ return {
256
+ body: {
257
+ expiry: result.expiry
258
+ }
259
+ };
260
+ }
261
+ /**
262
+ * Update the user's password.
263
+ * @param httpRequestContext The request context for the API.
264
+ * @param componentName The name of the component to use in the routes.
265
+ * @param request The request.
266
+ * @returns The response object with additional http response properties.
267
+ */
268
+ export async function authenticationUpdatePassword(httpRequestContext, componentName, request) {
269
+ Guards.object(ROUTES_SOURCE, "request", request);
270
+ Guards.object(ROUTES_SOURCE, "request.body", request.body);
271
+ const component = ComponentFactory.get(componentName);
272
+ await component.updatePassword(request.body.currentPassword, request.body.newPassword);
273
+ return {
274
+ statusCode: HttpStatusCode.noContent
275
+ };
276
+ }
277
+ /**
278
+ * Get the password policy.
279
+ * @param httpRequestContext The request context for the API.
280
+ * @param componentName The name of the component to use in the routes.
281
+ * @param request The request.
282
+ * @returns The response object with additional http response properties.
283
+ */
284
+ export async function authenticationPasswordPolicy(httpRequestContext, componentName, request) {
285
+ const component = ComponentFactory.get(componentName);
286
+ const result = await component.passwordPolicy();
287
+ return {
288
+ body: result
289
+ };
290
+ }
291
+ //# sourceMappingURL=entityStorageAuthenticationRoutes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entityStorageAuthenticationRoutes.js","sources":["../../../src/routes/entityStorageAuthenticationRoutes.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;;GAEG;AACH,MAAM,aAAa,GAAG,sBAAsB,CAAC;AAE7C;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAW;IACzC;QACC,IAAI,EAAE,gBAAgB;QACtB,WAAW,EAAE,+CAA+C;KAC5D;CACD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,gCAAgC,CAC/C,aAAqB,EACrB,aAAqB;IAErB,MAAM,UAAU,GAA8C;QAC7D,WAAW,EAAE,qBAAqB;QAClC,OAAO,EAAE,qBAAqB;QAC9B,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI;QAC/B,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,GAAG,aAAa,QAAQ;QAC9B,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,mBAAmB,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;QAChE,WAAW,EAAE;YACZ,IAAI,EAAE,eAAe;YACrB,QAAQ,EAAE;gBACT;oBACC,EAAE,EAAE,qBAAqB;oBACzB,WAAW,EAAE,qCAAqC;oBAClD,OAAO,EAAE;wBACR,IAAI,EAAE;4BACL,KAAK,EAAE,kBAAkB;4BACzB,QAAQ,EAAE,gBAAgB;yBAC1B;qBACD;iBACD;aACD;SACD;QACD,YAAY,EAAE;YACb;gBACC,IAAI,EAAE,gBAAgB;gBACtB,QAAQ,EAAE;oBACT;wBACC,EAAE,EAAE,sBAAsB;wBAC1B,WAAW,EAAE,qCAAqC;wBAClD,QAAQ,EAAE;4BACT,IAAI,EAAE;gCACL,MAAM,EAAE,aAAa;6BACrB;yBACD;qBACD;iBACD;aACD;YACD;gBACC,IAAI,EAAE,uBAAuB;aAC7B;SACD;QACD,QAAQ,EAAE,IAAI;KACd,CAAC;IAEF,MAAM,WAAW,GAAmD;QACnE,WAAW,EAAE,sBAAsB;QACnC,OAAO,EAAE,wBAAwB;QACjC,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI;QAC/B,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,GAAG,aAAa,SAAS;QAC/B,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,oBAAoB,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;QACjE,WAAW,EAAE;YACZ,IAAI,EAAE,gBAAgB;YACtB,QAAQ,EAAE;gBACT;oBACC,EAAE,EAAE,sBAAsB;oBAC1B,WAAW,EAAE,wCAAwC;oBACrD,OAAO,EAAE;wBACR,IAAI,EAAE;4BACL,KAAK,EAAE,sBAAsB;yBAC7B;qBACD;iBACD;aACD;SACD;QACD,YAAY,EAAE;YACb;gBACC,IAAI,EAAE,oBAAoB;aAC1B;SACD;KACD,CAAC;IAEF,MAAM,iBAAiB,GAA4D;QAClF,WAAW,EAAE,4BAA4B;QACzC,OAAO,EAAE,iCAAiC;QAC1C,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI;QAC/B,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,GAAG,aAAa,UAAU;QAChC,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,0BAA0B,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;QACvE,WAAW,EAAE;YACZ,IAAI,EAAE,sBAAsB;YAC5B,QAAQ,EAAE;gBACT;oBACC,EAAE,EAAE,4BAA4B;oBAChC,WAAW,EAAE,uCAAuC;oBACpD,OAAO,EAAE;wBACR,IAAI,EAAE;4BACL,KAAK,EAAE,sBAAsB;yBAC7B;qBACD;iBACD;aACD;SACD;QACD,YAAY,EAAE;YACb;gBACC,IAAI,EAAE,uBAAuB;gBAC7B,QAAQ,EAAE;oBACT;wBACC,EAAE,EAAE,6BAA6B;wBACjC,WAAW,EAAE,6CAA6C;wBAC1D,QAAQ,EAAE;4BACT,IAAI,EAAE;gCACL,MAAM,EAAE,aAAa;6BACrB;yBACD;qBACD;iBACD;aACD;YACD;gBACC,IAAI,EAAE,uBAAuB;aAC7B;SACD;KACD,CAAC;IAEF,MAAM,mBAAmB,GAA2D;QACnF,WAAW,EAAE,8BAA8B;QAC3C,OAAO,EAAE,oCAAoC;QAC7C,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI;QAC/B,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,GAAG,aAAa,WAAW;QACjC,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,4BAA4B,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;QACzE,WAAW,EAAE;YACZ,IAAI,EAAE,wBAAwB;YAC9B,QAAQ,EAAE;gBACT;oBACC,EAAE,EAAE,8BAA8B;oBAClC,WAAW,EAAE,oDAAoD;oBACjE,OAAO,EAAE;wBACR,IAAI,EAAE;4BACL,eAAe,EAAE,mBAAmB;4BACpC,WAAW,EAAE,mBAAmB;yBAChC;qBACD;iBACD;aACD;SACD;QACD,YAAY,EAAE;YACb;gBACC,IAAI,EAAE,oBAAoB;aAC1B;YACD;gBACC,IAAI,EAAE,uBAAuB;aAC7B;SACD;KACD,CAAC;IAEF,MAAM,mBAAmB,GAA2D;QACnF,WAAW,EAAE,8BAA8B;QAC3C,OAAO,EAAE,yBAAyB;QAClC,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI;QAC/B,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,GAAG,aAAa,SAAS;QAC/B,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,CAC9C,4BAA4B,CAAC,kBAAkB,EAAE,aAAa,EAAE,OAAO,CAAC;QACzE,WAAW,EAAE;YACZ,IAAI,EAAE,mBAAmB;SACzB;QACD,YAAY,EAAE;YACb;gBACC,IAAI,EAAE,yBAAyB;gBAC/B,QAAQ,EAAE;oBACT;wBACC,EAAE,EAAE,+BAA+B;wBACnC,WAAW,EAAE,+CAA+C;wBAC5D,QAAQ,EAAE;4BACT,IAAI,EAAE;gCACL,iBAAiB,EAAE,EAAE;gCACrB,iBAAiB,EAAE,GAAG;6BACtB;yBACD;qBACD;iBACD;aACD;SACD;QACD,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI;KAChB,CAAC;IAEF,OAAO,CAAC,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;AAC/F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,kBAAuC,EACvC,aAAqB,EACrB,OAAsB;IAEtB,MAAM,CAAC,MAAM,CAAgB,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAChE,MAAM,CAAC,MAAM,CAAwB,aAAa,EAAE,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAElF,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAA2B,aAAa,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEhF,iEAAiE;IACjE,+CAA+C;IAC/C,kBAAkB,CAAC,cAAc,CAAC,aAAa,GAAG,OAAO,CAAC;IAC1D,kBAAkB,CAAC,cAAc,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC;IAE3D,OAAO;QACN,IAAI,EAAE;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;SACrB;KACD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,kBAAuC,EACvC,aAAqB,EACrB,OAAuB;IAEvB,MAAM,CAAC,MAAM,CAAiB,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAEjE,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAA2B,aAAa,CAAC,CAAC;IAChF,MAAM,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAE5C,iEAAiE;IACjE,+CAA+C;IAC/C,kBAAkB,CAAC,cAAc,CAAC,aAAa,GAAG,QAAQ,CAAC;IAE3D,OAAO;QACN,UAAU,EAAE,cAAc,CAAC,SAAS;KACpC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC/C,kBAAuC,EACvC,aAAqB,EACrB,OAA6B;IAE7B,MAAM,CAAC,MAAM,CAAuB,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAEvE,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAA2B,aAAa,CAAC,CAAC;IAEhF,iFAAiF;IACjF,uCAAuC;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,IAAK,kBAAkB,CAAC,cAAc,CAAC,SAAoB,CAAC;IAC7F,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAE9C,iEAAiE;IACjE,+CAA+C;IAC/C,kBAAkB,CAAC,cAAc,CAAC,aAAa,GAAG,SAAS,CAAC;IAC5D,kBAAkB,CAAC,cAAc,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC;IAE3D,OAAO;QACN,IAAI,EAAE;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;SACrB;KACD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CACjD,kBAAuC,EACvC,aAAqB,EACrB,OAA+B;IAE/B,MAAM,CAAC,MAAM,CAAyB,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACzE,MAAM,CAAC,MAAM,CAAiC,aAAa,EAAE,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3F,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAA2B,aAAa,CAAC,CAAC;IAEhF,MAAM,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAEvF,OAAO;QACN,UAAU,EAAE,cAAc,CAAC,SAAS;KACpC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CACjD,kBAAuC,EACvC,aAAqB,EACrB,OAA0B;IAE1B,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAA2B,aAAa,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,cAAc,EAAE,CAAC;IAEhD,OAAO;QACN,IAAI,EAAE,MAAM;KACZ,CAAC;AACH,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIAuthenticationComponent,\n\tILoginRequest,\n\tILoginResponse,\n\tILogoutRequest,\n\tIPasswordPolicyResponse,\n\tIRefreshTokenRequest,\n\tIRefreshTokenResponse,\n\tIUpdatePasswordRequest\n} from \"@3sixty/api-auth-entity-storage-models\";\nimport type {\n\tIHttpRequestContext,\n\tINoContentRequest,\n\tINoContentResponse,\n\tIRestRoute,\n\tIRestRouteResponseOptions,\n\tITag,\n\tIUnauthorizedResponse\n} from \"@3sixty/api-models\";\nimport { ComponentFactory, Guards } from \"@3sixty/core\";\n\nimport { HttpStatusCode } from \"@3sixty/web\";\n\n/**\n * The source used when communicating about these routes.\n */\nconst ROUTES_SOURCE = \"authenticationRoutes\";\n\n/**\n * The tag to associate with the routes.\n */\nexport const tagsAuthentication: ITag[] = [\n\t{\n\t\tname: \"Authentication\",\n\t\tdescription: \"Authentication endpoints for the REST server.\"\n\t}\n];\n\n/**\n * The REST routes for authentication.\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated routes.\n */\nexport function generateRestRoutesAuthentication(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst loginRoute: IRestRoute<ILoginRequest, ILoginResponse> = {\n\t\toperationId: \"authenticationLogin\",\n\t\tsummary: \"Login to the server\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/login`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationLogin(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: \"ILoginRequest\",\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"loginRequestExample\",\n\t\t\t\t\tdescription: \"The request to login to the server.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\temail: \"user@example.com\",\n\t\t\t\t\t\t\tpassword: \"MyPassword123!\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"ILoginResponse\",\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"loginResponseExample\",\n\t\t\t\t\t\tdescription: \"The response for the login request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\texpiry: 1722514341067\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: \"IUnauthorizedResponse\"\n\t\t\t}\n\t\t],\n\t\tskipAuth: true\n\t};\n\n\tconst logoutRoute: IRestRoute<ILogoutRequest, INoContentResponse> = {\n\t\toperationId: \"authenticationLogout\",\n\t\tsummary: \"Logout from the server\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/logout`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationLogout(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: \"ILogoutRequest\",\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"logoutRequestExample\",\n\t\t\t\t\tdescription: \"The request to logout from the server.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\ttoken: \"eyJhbGciOiJIU...sw5c\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"INoContentResponse\"\n\t\t\t}\n\t\t]\n\t};\n\n\tconst refreshTokenRoute: IRestRoute<IRefreshTokenRequest, IRefreshTokenResponse> = {\n\t\toperationId: \"authenticationRefreshToken\",\n\t\tsummary: \"Refresh an authentication token\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/refresh`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationRefreshToken(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: \"IRefreshTokenRequest\",\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"refreshTokenRequestExample\",\n\t\t\t\t\tdescription: \"The request to refresh an auth token.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\ttoken: \"eyJhbGciOiJIU...sw5c\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"IRefreshTokenResponse\",\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"refreshTokenResponseExample\",\n\t\t\t\t\t\tdescription: \"The response for the refresh token request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\texpiry: 1722514341067\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: \"IUnauthorizedResponse\"\n\t\t\t}\n\t\t]\n\t};\n\n\tconst updatePasswordRoute: IRestRoute<IUpdatePasswordRequest, INoContentResponse> = {\n\t\toperationId: \"authenticationUpdatePassword\",\n\t\tsummary: \"Update the current user's password\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"PUT\",\n\t\tpath: `${baseRouteName}/password`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationUpdatePassword(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: \"IUpdatePasswordRequest\",\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tid: \"updatePasswordRequestExample\",\n\t\t\t\t\tdescription: \"The request to update the current user's password.\",\n\t\t\t\t\trequest: {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tcurrentPassword: \"MyNewPassword123!\",\n\t\t\t\t\t\t\tnewPassword: \"MyNewPassword123!\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"INoContentResponse\"\n\t\t\t},\n\t\t\t{\n\t\t\t\ttype: \"IUnauthorizedResponse\"\n\t\t\t}\n\t\t]\n\t};\n\n\tconst passwordPolicyRoute: IRestRoute<INoContentRequest, IPasswordPolicyResponse> = {\n\t\toperationId: \"authenticationPasswordPolicy\",\n\t\tsummary: \"Get the password policy\",\n\t\ttag: tagsAuthentication[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/policy`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tauthenticationPasswordPolicy(httpRequestContext, componentName, request),\n\t\trequestType: {\n\t\t\ttype: \"INoContentRequest\"\n\t\t},\n\t\tresponseType: [\n\t\t\t{\n\t\t\t\ttype: \"IPasswordPolicyResponse\",\n\t\t\t\texamples: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"passwordPolicyResponseExample\",\n\t\t\t\t\t\tdescription: \"The response for the password policy request.\",\n\t\t\t\t\t\tresponse: {\n\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\tminPasswordLength: 15,\n\t\t\t\t\t\t\t\tmaxPasswordLength: 128\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t],\n\t\tskipAuth: true,\n\t\tskipTenant: true\n\t};\n\n\treturn [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute, passwordPolicyRoute];\n}\n\n/**\n * Login to the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationLogin(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ILoginRequest\n): Promise<ILoginResponse & IRestRouteResponseOptions> {\n\tGuards.object<ILoginRequest>(ROUTES_SOURCE, \"request\", request);\n\tGuards.object<ILoginRequest[\"body\"]>(ROUTES_SOURCE, \"request.body\", request.body);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\tconst result = await component.login(request.body.email, request.body.password);\n\n\t// Need to give a hint to any auth processors about the operation\n\t// in case they need to manipulate the response\n\thttpRequestContext.processorState.authOperation = \"login\";\n\thttpRequestContext.processorState.authToken = result.token;\n\n\treturn {\n\t\tbody: {\n\t\t\texpiry: result.expiry\n\t\t}\n\t};\n}\n\n/**\n * Logout from the server.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationLogout(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ILogoutRequest\n): Promise<INoContentResponse & IRestRouteResponseOptions> {\n\tGuards.object<ILogoutRequest>(ROUTES_SOURCE, \"request\", request);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\tawait component.logout(request.body?.token);\n\n\t// Need to give a hint to any auth processors about the operation\n\t// in case they need to manipulate the response\n\thttpRequestContext.processorState.authOperation = \"logout\";\n\n\treturn {\n\t\tstatusCode: HttpStatusCode.noContent\n\t};\n}\n\n/**\n * Refresh the login token.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationRefreshToken(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IRefreshTokenRequest\n): Promise<IRefreshTokenResponse & IRestRouteResponseOptions> {\n\tGuards.object<IRefreshTokenRequest>(ROUTES_SOURCE, \"request\", request);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\n\t// If the token is not in the body, then maybe an auth processor has extracted it\n\t// and stored it in the processor state\n\tconst token = request.body?.token ?? (httpRequestContext.processorState.authToken as string);\n\tconst result = await component.refresh(token);\n\n\t// Need to give a hint to any auth processors about the operation\n\t// in case they need to manipulate the response\n\thttpRequestContext.processorState.authOperation = \"refresh\";\n\thttpRequestContext.processorState.authToken = result.token;\n\n\treturn {\n\t\tbody: {\n\t\t\texpiry: result.expiry\n\t\t}\n\t};\n}\n\n/**\n * Update the user's password.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationUpdatePassword(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IUpdatePasswordRequest\n): Promise<INoContentResponse> {\n\tGuards.object<IUpdatePasswordRequest>(ROUTES_SOURCE, \"request\", request);\n\tGuards.object<IUpdatePasswordRequest[\"body\"]>(ROUTES_SOURCE, \"request.body\", request.body);\n\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\n\tawait component.updatePassword(request.body.currentPassword, request.body.newPassword);\n\n\treturn {\n\t\tstatusCode: HttpStatusCode.noContent\n\t};\n}\n\n/**\n * Get the password policy.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use in the routes.\n * @param request The request.\n * @returns The response object with additional http response properties.\n */\nexport async function authenticationPasswordPolicy(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: INoContentRequest\n): Promise<IPasswordPolicyResponse & IRestRouteResponseOptions> {\n\tconst component = ComponentFactory.get<IAuthenticationComponent>(componentName);\n\tconst result = await component.passwordPolicy();\n\n\treturn {\n\t\tbody: result\n\t};\n}\n"]}
@@ -0,0 +1,13 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { EntitySchemaFactory, EntitySchemaHelper } from "@3sixty/entity";
4
+ import { AuthenticationUser } from "./entities/authenticationUser.js";
5
+ import { AuthenticationUserV0 } from "./entities/authenticationUserV0.js";
6
+ /**
7
+ * Initialize the schema for the authentication service.
8
+ */
9
+ export function initSchema() {
10
+ EntitySchemaFactory.register("AuthenticationUser", () => EntitySchemaHelper.getSchema(AuthenticationUser));
11
+ EntitySchemaFactory.register("AuthenticationUserV0", () => EntitySchemaHelper.getSchema(AuthenticationUserV0));
12
+ }
13
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sources":["../../src/schema.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE1E;;GAEG;AACH,MAAM,UAAU,UAAU;IACzB,mBAAmB,CAAC,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE,CACvD,kBAAkB,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAChD,CAAC;IACF,mBAAmB,CAAC,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE,CACzD,kBAAkB,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAClD,CAAC;AACH,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { EntitySchemaFactory, EntitySchemaHelper } from \"@3sixty/entity\";\n\nimport { AuthenticationUser } from \"./entities/authenticationUser.js\";\nimport { AuthenticationUserV0 } from \"./entities/authenticationUserV0.js\";\n\n/**\n * Initialize the schema for the authentication service.\n */\nexport function initSchema(): void {\n\tEntitySchemaFactory.register(\"AuthenticationUser\", () =>\n\t\tEntitySchemaHelper.getSchema(AuthenticationUser)\n\t);\n\tEntitySchemaFactory.register(\"AuthenticationUserV0\", () =>\n\t\tEntitySchemaHelper.getSchema(AuthenticationUserV0)\n\t);\n}\n"]}
@@ -0,0 +1,324 @@
1
+ import { AuthAuditEvent } from "@3sixty/api-auth-models";
2
+ import { ForbiddenError, HttpContextIdKeys } from "@3sixty/api-models";
3
+ import { ContextIdKeys, ContextIdStore } from "@3sixty/context";
4
+ import { BaseError, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, RandomHelper } from "@3sixty/core";
5
+ import { PasswordGenerator, PasswordValidator } from "@3sixty/crypto";
6
+ import { EntityStorageConnectorFactory } from "@3sixty/entity-storage-models";
7
+ import { PasswordHelper } from "../utils/passwordHelper.js";
8
+ /**
9
+ * Implementation of the authentication component using entity storage.
10
+ */
11
+ export class EntityStorageAuthenticationAdminService {
12
+ /**
13
+ * Runtime name for the class.
14
+ */
15
+ static CLASS_NAME = "EntityStorageAuthenticationAdminService";
16
+ /**
17
+ * The default role that grants escalated privilege.
18
+ */
19
+ static DEFAULT_ESCALATED_PRIVILEGE_ROLE = "global-admin";
20
+ /**
21
+ * The default authorization model ID used to look up roles.
22
+ */
23
+ static DEFAULT_AUTHORIZATION_MODEL_ID = "system";
24
+ /**
25
+ * The entity storage for users.
26
+ * @internal
27
+ */
28
+ _userEntityStorage;
29
+ /**
30
+ * The audit component.
31
+ * @internal
32
+ */
33
+ _authenticationAuditService;
34
+ /**
35
+ * The authorization component used to guard accounts with escalated privilege.
36
+ * @internal
37
+ */
38
+ _authorizationComponent;
39
+ /**
40
+ * The role that grants escalated privilege.
41
+ * @internal
42
+ */
43
+ _escalatedPrivilegeRole;
44
+ /**
45
+ * The authorization model ID used to look up roles.
46
+ * @internal
47
+ */
48
+ _authorizationModelId;
49
+ /**
50
+ * The minimum password length.
51
+ * @internal
52
+ */
53
+ _minPasswordLength;
54
+ /**
55
+ * The maximum password length.
56
+ * @internal
57
+ */
58
+ _maxPasswordLength;
59
+ /**
60
+ * Create a new instance of EntityStorageAuthentication.
61
+ * @param options The dependencies for the identity connector.
62
+ */
63
+ constructor(options) {
64
+ this._userEntityStorage = EntityStorageConnectorFactory.get(options?.userEntityStorageType ?? "authentication-user");
65
+ this._authenticationAuditService = ComponentFactory.getIfExists(options?.authenticationAuditServiceType ?? "authentication-audit");
66
+ this._authorizationComponent = ComponentFactory.getIfExists(options?.authorizationComponentType ?? "authorization");
67
+ this._minPasswordLength = options?.config?.minPasswordLength;
68
+ this._maxPasswordLength = options?.config?.maxPasswordLength;
69
+ this._escalatedPrivilegeRole =
70
+ options?.config?.escalatedPrivilegeRole ??
71
+ EntityStorageAuthenticationAdminService.DEFAULT_ESCALATED_PRIVILEGE_ROLE;
72
+ this._authorizationModelId =
73
+ options?.config?.authorizationModelId ??
74
+ EntityStorageAuthenticationAdminService.DEFAULT_AUTHORIZATION_MODEL_ID;
75
+ }
76
+ /**
77
+ * Returns the class name of the component.
78
+ * @returns The class name of the component.
79
+ */
80
+ className() {
81
+ return EntityStorageAuthenticationAdminService.CLASS_NAME;
82
+ }
83
+ /**
84
+ * Create a login for the user.
85
+ * @param user The user to create.
86
+ * @returns A promise that resolves when the user account has been created and the audit entry recorded.
87
+ */
88
+ async create(user) {
89
+ Guards.object(EntityStorageAuthenticationAdminService.CLASS_NAME, "user", user);
90
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.email", user.email);
91
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.password", user.password);
92
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.userIdentity", user.userIdentity);
93
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.organizationIdentity", user.organizationIdentity);
94
+ try {
95
+ PasswordValidator.validatePassword(user.password, {
96
+ minLength: this._minPasswordLength,
97
+ maxLength: this._maxPasswordLength
98
+ });
99
+ const existingUser = await this._userEntityStorage.get(user.email);
100
+ if (Is.object(existingUser)) {
101
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userExists");
102
+ }
103
+ // Roles belong to the identity, so a login mapped to an escalated identity inherits them.
104
+ await this.guardEscalatedPrivilegeTarget(user.userIdentity);
105
+ const saltBytes = RandomHelper.generate(16);
106
+ const passwordBytes = Converter.utf8ToBytes(user.password);
107
+ const hashedPassword = await PasswordGenerator.hashPassword(passwordBytes, saltBytes);
108
+ const newUser = {
109
+ email: user.email,
110
+ salt: Converter.bytesToBase64(saltBytes),
111
+ password: hashedPassword,
112
+ identity: user.userIdentity,
113
+ organization: user.organizationIdentity,
114
+ passwordVersion: 0
115
+ };
116
+ await this._userEntityStorage.set(newUser);
117
+ const contextIds = await ContextIdStore.getContextIds();
118
+ const requestorTenantId = contextIds?.[ContextIdKeys.Tenant];
119
+ const originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant];
120
+ await this._authenticationAuditService?.create({
121
+ actorId: user.email,
122
+ event: AuthAuditEvent.AccountCreated,
123
+ data: {
124
+ userIdentity: user.userIdentity,
125
+ organizationIdentity: user.organizationIdentity,
126
+ tenantId: requestorTenantId,
127
+ originalTenantId
128
+ }
129
+ });
130
+ }
131
+ catch (error) {
132
+ if (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {
133
+ throw error;
134
+ }
135
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "createUserFailed", undefined, error);
136
+ }
137
+ }
138
+ /**
139
+ * Update a login for the user.
140
+ * @param user The user to update.
141
+ * @returns A promise that resolves when the user account has been updated and the audit entry recorded.
142
+ */
143
+ async update(user) {
144
+ Guards.object(EntityStorageAuthenticationAdminService.CLASS_NAME, "user", user);
145
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.email", user.email);
146
+ if (!Is.empty(user.userIdentity)) {
147
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.userIdentity", user.userIdentity);
148
+ }
149
+ if (!Is.empty(user.organizationIdentity)) {
150
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "user.organizationIdentity", user.organizationIdentity);
151
+ }
152
+ try {
153
+ const existingUser = await this._userEntityStorage.get(user.email);
154
+ if (!Is.object(existingUser)) {
155
+ throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", user.email);
156
+ }
157
+ await this.guardEscalatedPrivilegeTarget(existingUser.identity);
158
+ if (Is.stringValue(user.userIdentity) && user.userIdentity !== existingUser.identity) {
159
+ await this.guardEscalatedPrivilegeTarget(user.userIdentity);
160
+ }
161
+ const updatedFields = [];
162
+ if (user.userIdentity !== undefined && user.userIdentity !== existingUser.identity) {
163
+ updatedFields.push("userIdentity");
164
+ }
165
+ if (user.organizationIdentity !== undefined &&
166
+ user.organizationIdentity !== existingUser.organization) {
167
+ updatedFields.push("organizationIdentity");
168
+ }
169
+ existingUser.identity = user.userIdentity ?? existingUser.identity;
170
+ existingUser.organization = user.organizationIdentity ?? existingUser.organization;
171
+ await this._userEntityStorage.set(existingUser);
172
+ const contextIds = await ContextIdStore.getContextIds();
173
+ const requestorTenantId = contextIds?.[ContextIdKeys.Tenant];
174
+ const originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant];
175
+ await this._authenticationAuditService?.create({
176
+ actorId: existingUser.email,
177
+ event: AuthAuditEvent.AccountUpdated,
178
+ data: {
179
+ updatedFields,
180
+ userIdentity: existingUser.identity,
181
+ organizationIdentity: existingUser.organization,
182
+ tenantId: requestorTenantId,
183
+ originalTenantId
184
+ }
185
+ });
186
+ }
187
+ catch (error) {
188
+ if (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {
189
+ throw error;
190
+ }
191
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "updateUserFailed", undefined, error);
192
+ }
193
+ }
194
+ /**
195
+ * Get a user by email.
196
+ * @param email The email address of the user to get.
197
+ * @returns The user details.
198
+ */
199
+ async get(email) {
200
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "email", email);
201
+ try {
202
+ const user = await this._userEntityStorage.get(email);
203
+ if (!Is.object(user)) {
204
+ throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", email);
205
+ }
206
+ return {
207
+ email: user.email,
208
+ userIdentity: user.identity,
209
+ organizationIdentity: user.organization
210
+ };
211
+ }
212
+ catch (error) {
213
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "getUserFailed", undefined, error);
214
+ }
215
+ }
216
+ /**
217
+ * Get a user by identity.
218
+ * @param identity The identity of the user to get.
219
+ * @returns The user details.
220
+ */
221
+ async getByIdentity(identity) {
222
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "identity", identity);
223
+ try {
224
+ const user = await this._userEntityStorage.get(identity, "identity");
225
+ if (!Is.object(user)) {
226
+ throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", identity);
227
+ }
228
+ return {
229
+ email: user.email,
230
+ userIdentity: user.identity,
231
+ organizationIdentity: user.organization
232
+ };
233
+ }
234
+ catch (error) {
235
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "getUserFailed", undefined, error);
236
+ }
237
+ }
238
+ /**
239
+ * Remove the current user.
240
+ * @param email The email address of the user to remove.
241
+ * @returns A promise that resolves when the user account has been removed and the audit entry recorded.
242
+ */
243
+ async remove(email) {
244
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "email", email);
245
+ try {
246
+ const user = await this._userEntityStorage.get(email);
247
+ if (!Is.object(user)) {
248
+ throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", email);
249
+ }
250
+ await this.guardEscalatedPrivilegeTarget(user.identity);
251
+ await this._userEntityStorage.remove(email);
252
+ const contextIds = await ContextIdStore.getContextIds();
253
+ const requestorTenantId = contextIds?.[ContextIdKeys.Tenant];
254
+ const originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant];
255
+ await this._authenticationAuditService?.create({
256
+ actorId: email,
257
+ event: AuthAuditEvent.AccountDeleted,
258
+ data: {
259
+ userIdentity: user.identity,
260
+ organizationIdentity: user.organization,
261
+ tenantId: requestorTenantId,
262
+ originalTenantId
263
+ }
264
+ });
265
+ }
266
+ catch (error) {
267
+ if (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {
268
+ throw error;
269
+ }
270
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "removeUserFailed", undefined, error);
271
+ }
272
+ }
273
+ /**
274
+ * Update the user's password.
275
+ * @param email The email address of the user to update.
276
+ * @param newPassword The new password for the user.
277
+ * @param currentPassword The current password, optional, if supplied will check against existing.
278
+ * @returns A promise that resolves when the password has been updated.
279
+ */
280
+ async updatePassword(email, newPassword, currentPassword) {
281
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "email", email);
282
+ Guards.stringValue(EntityStorageAuthenticationAdminService.CLASS_NAME, "newPassword", newPassword);
283
+ try {
284
+ const user = await this._userEntityStorage.get(email);
285
+ if (!Is.object(user)) {
286
+ throw new NotFoundError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userNotFound", email);
287
+ }
288
+ await this.guardEscalatedPrivilegeTarget(user.identity);
289
+ await PasswordHelper.updatePassword(this._userEntityStorage, this._authenticationAuditService, user, newPassword, currentPassword, {
290
+ minPasswordLength: this._minPasswordLength,
291
+ maxPasswordLength: this._maxPasswordLength
292
+ });
293
+ }
294
+ catch (error) {
295
+ if (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) {
296
+ throw error;
297
+ }
298
+ throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "updatePasswordFailed", undefined, error);
299
+ }
300
+ }
301
+ /**
302
+ * Ensure the caller holds escalated privilege when the target identity holds it.
303
+ * @param targetIdentity The identity of the account being modified.
304
+ * @throws ForbiddenError if the target holds escalated privilege and the caller does not.
305
+ * @internal
306
+ */
307
+ async guardEscalatedPrivilegeTarget(targetIdentity) {
308
+ if (Is.empty(this._authorizationComponent)) {
309
+ return;
310
+ }
311
+ const targetIsEscalated = await this._authorizationComponent.hasRoleForSubject(this._authorizationModelId, targetIdentity, this._escalatedPrivilegeRole);
312
+ if (!targetIsEscalated) {
313
+ return;
314
+ }
315
+ const contextIds = await ContextIdStore.getContextIds();
316
+ const callerIdentity = contextIds?.[ContextIdKeys.User];
317
+ const callerIsEscalated = Is.stringValue(callerIdentity) &&
318
+ (await this._authorizationComponent.hasRoleForSubject(this._authorizationModelId, callerIdentity, this._escalatedPrivilegeRole));
319
+ if (!callerIsEscalated) {
320
+ throw new ForbiddenError(EntityStorageAuthenticationAdminService.CLASS_NAME, "insufficientRoleForEscalatedTarget", { roleName: this._escalatedPrivilegeRole });
321
+ }
322
+ }
323
+ }
324
+ //# sourceMappingURL=entityStorageAuthenticationAdminService.js.map