@webda/core 4.0.0-beta.3 → 4.0.0-beta.5

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 (158) hide show
  1. package/lib/application/application.d.ts +31 -0
  2. package/lib/application/application.d.ts.map +1 -1
  3. package/lib/application/application.js +55 -2
  4. package/lib/application/application.js.map +1 -1
  5. package/lib/application/deployment.d.ts +93 -0
  6. package/lib/application/deployment.d.ts.map +1 -0
  7. package/lib/application/deployment.js +99 -0
  8. package/lib/application/deployment.js.map +1 -0
  9. package/lib/application/unpackedapplication.d.ts.map +1 -1
  10. package/lib/application/unpackedapplication.js +9 -1
  11. package/lib/application/unpackedapplication.js.map +1 -1
  12. package/lib/bin/cli.d.ts +36 -1
  13. package/lib/bin/cli.d.ts.map +1 -1
  14. package/lib/bin/cli.js +108 -2
  15. package/lib/bin/cli.js.map +1 -1
  16. package/lib/contexts/execution.js +1 -1
  17. package/lib/contexts/execution.js.map +1 -1
  18. package/lib/contexts/operationcontext.d.ts +3 -0
  19. package/lib/contexts/operationcontext.d.ts.map +1 -1
  20. package/lib/contexts/operationcontext.js +6 -3
  21. package/lib/contexts/operationcontext.js.map +1 -1
  22. package/lib/core/core.js +1 -1
  23. package/lib/core/core.js.map +1 -1
  24. package/lib/core/hooks.d.ts +22 -0
  25. package/lib/core/hooks.d.ts.map +1 -1
  26. package/lib/core/hooks.js +24 -0
  27. package/lib/core/hooks.js.map +1 -1
  28. package/lib/core/operations.d.ts.map +1 -1
  29. package/lib/core/operations.js +13 -11
  30. package/lib/core/operations.js.map +1 -1
  31. package/lib/deployers/deployer.d.ts +157 -0
  32. package/lib/deployers/deployer.d.ts.map +1 -0
  33. package/lib/deployers/deployer.js +81 -0
  34. package/lib/deployers/deployer.js.map +1 -0
  35. package/lib/deployers/git.d.ts +27 -0
  36. package/lib/deployers/git.d.ts.map +1 -0
  37. package/lib/deployers/git.js +66 -0
  38. package/lib/deployers/git.js.map +1 -0
  39. package/lib/deployers/packager.d.ts +85 -0
  40. package/lib/deployers/packager.d.ts.map +1 -0
  41. package/lib/deployers/packager.js +259 -0
  42. package/lib/deployers/packager.js.map +1 -0
  43. package/lib/index.d.ts +10 -4
  44. package/lib/index.d.ts.map +1 -1
  45. package/lib/index.js +10 -4
  46. package/lib/index.js.map +1 -1
  47. package/lib/models/aclmodel.d.ts +32 -5
  48. package/lib/models/aclmodel.d.ts.map +1 -1
  49. package/lib/models/aclmodel.js +34 -9
  50. package/lib/models/aclmodel.js.map +1 -1
  51. package/lib/models/coremodel.model.d.ts.map +1 -1
  52. package/lib/models/coremodel.model.js.map +1 -1
  53. package/lib/models/decorator.d.ts +4 -0
  54. package/lib/models/decorator.d.ts.map +1 -1
  55. package/lib/models/decorator.js +4 -0
  56. package/lib/models/decorator.js.map +1 -1
  57. package/lib/models/deployment.d.ts +2 -21
  58. package/lib/models/deployment.d.ts.map +1 -1
  59. package/lib/models/encrypted.model.d.ts +46 -0
  60. package/lib/models/encrypted.model.d.ts.map +1 -0
  61. package/lib/models/encrypted.model.js +87 -0
  62. package/lib/models/encrypted.model.js.map +1 -0
  63. package/lib/models/ident.model.d.ts +103 -64
  64. package/lib/models/ident.model.d.ts.map +1 -1
  65. package/lib/models/ident.model.js +168 -76
  66. package/lib/models/ident.model.js.map +1 -1
  67. package/lib/models/ownermodel.model.d.ts +25 -3
  68. package/lib/models/ownermodel.model.d.ts.map +1 -1
  69. package/lib/models/ownermodel.model.js +46 -10
  70. package/lib/models/ownermodel.model.js.map +1 -1
  71. package/lib/models/password.model.d.ts +78 -0
  72. package/lib/models/password.model.d.ts.map +1 -0
  73. package/lib/models/password.model.js +171 -0
  74. package/lib/models/password.model.js.map +1 -0
  75. package/lib/models/permissions.d.ts +236 -0
  76. package/lib/models/permissions.d.ts.map +1 -0
  77. package/lib/models/permissions.js +495 -0
  78. package/lib/models/permissions.js.map +1 -0
  79. package/lib/models/refreshtoken.model.d.ts +30 -0
  80. package/lib/models/refreshtoken.model.d.ts.map +1 -0
  81. package/lib/models/refreshtoken.model.js +29 -0
  82. package/lib/models/refreshtoken.model.js.map +1 -0
  83. package/lib/models/registry.model.d.ts.map +1 -1
  84. package/lib/models/registry.model.js.map +1 -1
  85. package/lib/models/rolemodel.model.d.ts.map +1 -1
  86. package/lib/models/rolemodel.model.js +1 -2
  87. package/lib/models/rolemodel.model.js.map +1 -1
  88. package/lib/models/simpleuser.model.d.ts.map +1 -1
  89. package/lib/models/simpleuser.model.js +2 -1
  90. package/lib/models/simpleuser.model.js.map +1 -1
  91. package/lib/models/user.model.d.ts +33 -21
  92. package/lib/models/user.model.d.ts.map +1 -1
  93. package/lib/models/user.model.js +81 -26
  94. package/lib/models/user.model.js.map +1 -1
  95. package/lib/rest/restoperationstransport.service.d.ts.map +1 -1
  96. package/lib/rest/restoperationstransport.service.js +7 -5
  97. package/lib/rest/restoperationstransport.service.js.map +1 -1
  98. package/lib/services/audit.model.d.ts +2 -2
  99. package/lib/services/audit.model.d.ts.map +1 -1
  100. package/lib/services/audit.model.js +16 -6
  101. package/lib/services/audit.model.js.map +1 -1
  102. package/lib/services/binary.service.d.ts +42 -4
  103. package/lib/services/binary.service.d.ts.map +1 -1
  104. package/lib/services/binary.service.js +33 -13
  105. package/lib/services/binary.service.js.map +1 -1
  106. package/lib/services/domainservice.service.d.ts +111 -1
  107. package/lib/services/domainservice.service.d.ts.map +1 -1
  108. package/lib/services/domainservice.service.js +303 -50
  109. package/lib/services/domainservice.service.js.map +1 -1
  110. package/lib/services/iauthentication.d.ts +196 -3
  111. package/lib/services/iauthentication.d.ts.map +1 -1
  112. package/lib/services/servicecommands.d.ts.map +1 -1
  113. package/lib/services/servicecommands.js +17 -11
  114. package/lib/services/servicecommands.js.map +1 -1
  115. package/lib/services/token.service.d.ts +95 -0
  116. package/lib/services/token.service.d.ts.map +1 -0
  117. package/lib/services/token.service.js +182 -0
  118. package/lib/services/token.service.js.map +1 -0
  119. package/lib/session/manager.model.d.ts +16 -0
  120. package/lib/session/manager.model.d.ts.map +1 -1
  121. package/lib/session/manager.model.js +58 -1
  122. package/lib/session/manager.model.js.map +1 -1
  123. package/lib/session/session.d.ts +44 -1
  124. package/lib/session/session.d.ts.map +1 -1
  125. package/lib/session/session.js +51 -2
  126. package/lib/session/session.js.map +1 -1
  127. package/lib/stores/memory.service.d.ts.map +1 -1
  128. package/lib/stores/memory.service.js +4 -3
  129. package/lib/stores/memory.service.js.map +1 -1
  130. package/lib/templates/templates.d.ts +8 -0
  131. package/lib/templates/templates.d.ts.map +1 -1
  132. package/lib/templates/templates.js +10 -1
  133. package/lib/templates/templates.js.map +1 -1
  134. package/lib/test/behavior-fixture.model.d.ts +30 -0
  135. package/lib/test/behavior-fixture.model.d.ts.map +1 -0
  136. package/lib/test/behavior-fixture.model.js +63 -0
  137. package/lib/test/behavior-fixture.model.js.map +1 -0
  138. package/lib/test/legacyident.fixture.model.d.ts +98 -0
  139. package/lib/test/legacyident.fixture.model.d.ts.map +1 -0
  140. package/lib/test/legacyident.fixture.model.js +125 -0
  141. package/lib/test/legacyident.fixture.model.js.map +1 -0
  142. package/lib/test/objects.d.ts +2 -2
  143. package/lib/test/objects.d.ts.map +1 -1
  144. package/lib/test/objects.js +2 -2
  145. package/lib/test/objects.js.map +1 -1
  146. package/package.json +32 -11
  147. package/test/config-cached.json +2 -1
  148. package/test/config.json +3 -1
  149. package/test/my-cnf.json +1 -1
  150. package/webda.module.json +656 -390
  151. package/lib/services/authentication.service.d.ts +0 -442
  152. package/lib/services/authentication.service.d.ts.map +0 -1
  153. package/lib/services/authentication.service.js +0 -932
  154. package/lib/services/authentication.service.js.map +0 -1
  155. package/lib/services/oauth.d.ts +0 -250
  156. package/lib/services/oauth.d.ts.map +0 -1
  157. package/lib/services/oauth.js +0 -405
  158. package/lib/services/oauth.js.map +0 -1
@@ -0,0 +1,46 @@
1
+ import { WEBDA_STORAGE } from "@webda/models";
2
+ /**
3
+ * A model field encrypted at rest with the CryptoService
4
+ *
5
+ * Only the ciphertext is stored (`__ciphertext`, server-only: `__` keys never reach a public output). The value is
6
+ * JSON-serialised, encrypted with the current symmetric key (AES, with a random IV) and wrapped in a JWT signed with
7
+ * that key, so tampering is detected; values encrypted before a key rotation still decrypt while the CryptoService
8
+ * keeps the old key.
9
+ *
10
+ * ```typescript
11
+ * class MyModel extends UuidModel {
12
+ * secret: EncryptedField<{ apiKey: string }>;
13
+ * }
14
+ * await model.secret.set({ apiKey: "..." });
15
+ * await model.save();
16
+ * const { apiKey } = await model.secret.get();
17
+ * ```
18
+ * @WebdaBehavior Webda/Encrypted
19
+ */
20
+ export declare class EncryptedField<T = any> {
21
+ [WEBDA_STORAGE]: Record<string, any>;
22
+ /** Encrypted value (a JWT wrapping the AES ciphertext), never output */
23
+ __ciphertext?: string;
24
+ /**
25
+ * @returns true when a value is stored
26
+ */
27
+ isSet(): boolean;
28
+ /**
29
+ * Encrypt and store a value (does not save the model); undefined clears it
30
+ * @param value - value to store, JSON-serialisable
31
+ */
32
+ set(value: T | undefined): Promise<void>;
33
+ /**
34
+ * Decrypt the stored value
35
+ * @returns the value, undefined when none is stored
36
+ * @throws Error when the ciphertext is invalid (tampered, or encrypted with an unknown key)
37
+ */
38
+ get(): Promise<T | undefined>;
39
+ /**
40
+ * Remove the stored value (does not save the model)
41
+ */
42
+ clear(): void;
43
+ get parent(): any;
44
+ toJSON(): Record<string, any>;
45
+ }
46
+ //# sourceMappingURL=encrypted.model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encrypted.model.d.ts","sourceRoot":"","sources":["../../src/models/encrypted.model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAe9C;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,cAAc,CAAC,CAAC,GAAG,GAAG;IACjC,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAM;IAC1C,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,KAAK,IAAI,OAAO,CAEf;IAED;;;OAGG;IACG,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAM7C;IAED;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAKlC;IAED;;OAEG;IACH,KAAK,IAAI,IAAI,CAEZ;IACD,IAAI,MAAM,QAAgD;IAC1D,MAAM,wBAcL;CACF"}
@@ -0,0 +1,87 @@
1
+ import { WEBDA_STORAGE } from "@webda/models";
2
+ import { useDynamicService } from "../core/hooks.js";
3
+ /**
4
+ * @returns the CryptoService
5
+ * @throws Error when no CryptoService is available
6
+ */
7
+ function crypto() {
8
+ const service = useDynamicService("CryptoService");
9
+ if (!service) {
10
+ throw new Error("Encrypted fields require the CryptoService");
11
+ }
12
+ return service;
13
+ }
14
+ /**
15
+ * A model field encrypted at rest with the CryptoService
16
+ *
17
+ * Only the ciphertext is stored (`__ciphertext`, server-only: `__` keys never reach a public output). The value is
18
+ * JSON-serialised, encrypted with the current symmetric key (AES, with a random IV) and wrapped in a JWT signed with
19
+ * that key, so tampering is detected; values encrypted before a key rotation still decrypt while the CryptoService
20
+ * keeps the old key.
21
+ *
22
+ * ```typescript
23
+ * class MyModel extends UuidModel {
24
+ * secret: EncryptedField<{ apiKey: string }>;
25
+ * }
26
+ * await model.secret.set({ apiKey: "..." });
27
+ * await model.save();
28
+ * const { apiKey } = await model.secret.get();
29
+ * ```
30
+ * @WebdaBehavior Webda/Encrypted
31
+ */
32
+ export class EncryptedField {
33
+ [WEBDA_STORAGE] = {};
34
+ /** Encrypted value (a JWT wrapping the AES ciphertext), never output */
35
+ __ciphertext;
36
+ /**
37
+ * @returns true when a value is stored
38
+ */
39
+ isSet() {
40
+ return typeof this.__ciphertext === "string" && this.__ciphertext.length > 0;
41
+ }
42
+ /**
43
+ * Encrypt and store a value (does not save the model); undefined clears it
44
+ * @param value - value to store, JSON-serialisable
45
+ */
46
+ async set(value) {
47
+ if (value === undefined) {
48
+ this.clear();
49
+ return;
50
+ }
51
+ this.__ciphertext = await crypto().encrypt(value);
52
+ }
53
+ /**
54
+ * Decrypt the stored value
55
+ * @returns the value, undefined when none is stored
56
+ * @throws Error when the ciphertext is invalid (tampered, or encrypted with an unknown key)
57
+ */
58
+ async get() {
59
+ if (!this.isSet()) {
60
+ return undefined;
61
+ }
62
+ return crypto().decrypt(this.__ciphertext);
63
+ }
64
+ /**
65
+ * Remove the stored value (does not save the model)
66
+ */
67
+ clear() {
68
+ this.__ciphertext = undefined;
69
+ }
70
+ get parent() { return this[WEBDA_STORAGE]["__parent__"]; }
71
+ toJSON() {
72
+ const result = {};
73
+ for (const key of Object.keys(this)) {
74
+ result[key] = this[key];
75
+ }
76
+ const storage = this[WEBDA_STORAGE];
77
+ if (storage) {
78
+ for (const key of Object.keys(storage)) {
79
+ if (key !== "__parent__") {
80
+ result[key] = storage[key];
81
+ }
82
+ }
83
+ }
84
+ return result;
85
+ }
86
+ }
87
+ //# sourceMappingURL=encrypted.model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encrypted.model.js","sourceRoot":"","sources":["../../src/models/encrypted.model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;GAGG;AACH,SAAS,MAAM;IACb,MAAM,OAAO,GAAG,iBAAiB,CAAM,eAAe,CAAC,CAAC;IACxD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,cAAc;IACzB,CAAC,aAAa,CAAC,GAAwB,EAAE,CAAC;IAC1C,wEAAwE;IACxE,YAAY,CAAU;IAEtB;;OAEG;IACH,KAAK;QACH,OAAO,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/E,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAC,KAAoB;QAC5B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,MAAM,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG;QACP,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YAClB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;IAChC,CAAC;IACD,IAAI,MAAM,KAAK,OAAO,IAAI,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,MAAM;QACJ,MAAM,MAAM,GAAwB,EAAE,CAAC;QACvC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,GAAG,CAAC,GAAI,IAAY,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,OAAO,GAAqC,IAAY,CAAC,aAAa,CAAC,CAAC;QAC9E,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvC,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;oBACzB,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
@@ -1,89 +1,128 @@
1
- import type { Settable } from "@webda/models";
2
- import { OwnerModel } from "./ownermodel.model.js";
3
- import type { User } from "./user.model.js";
4
- /** OAuth tokens associated with an identity provider */
5
- export declare class IdentTokens {
6
- refresh: string;
7
- access: string;
1
+ import type { PrimaryKeyType } from "@webda/models";
2
+ import { Model, ModelLink, WEBDA_PRIMARY_KEY, WEBDA_PRIMARY_KEY_SEPARATOR, type Settable } from "@webda/models";
3
+ import * as WebdaError from "../errors/errors.js";
4
+ import type { IOperationContext } from "../contexts/icontext.js";
5
+ import { User } from "./user.model.js";
6
+ import { EncryptedField } from "./encrypted.model.js";
7
+ /** Tokens (credentials) of an identity provider, as the provider returns them */
8
+ export interface IdentTokens {
9
+ /** Access token */
10
+ access_token?: string;
11
+ /** Refresh token */
12
+ refresh_token?: string;
13
+ /** Any other provider field */
14
+ [key: string]: any;
15
+ }
16
+ /** Email sending throttle state of an ident (login failures are top-level `Ident` attributes) */
17
+ export interface IdentThrottle {
18
+ /** Timestamp of the last email sent */
19
+ lastSentAt?: number;
20
+ }
21
+ /** Raised for malformed ident keys (400 INVALID_IDENT) */
22
+ export declare class InvalidIdent extends WebdaError.HttpError {
23
+ /** @param message - error message */
24
+ constructor(message?: string);
8
25
  }
9
26
  /**
10
- * First basic model for Ident
11
- * @class
27
+ * An identity linked to a user: `<providerUid>:<provider>`
12
28
  * @WebdaModel
13
29
  */
14
- export declare class Ident extends OwnerModel {
15
- /** Create a new Ident
16
- * @param data - initial data
17
- */
18
- constructor(data?: Settable<Ident>);
19
- /**
20
- * Type of the ident
21
- */
22
- _type: string;
23
- /**
24
- * Uid on the provider
25
- */
26
- uid: string;
27
- /**
28
- * Provider profile
29
- */
30
- __profile: any;
31
- /**
32
- * Tokens for this ident
33
- */
34
- __tokens: IdentTokens;
35
- /**
36
- * Last time the ident was used
37
- */
38
- get _lastUsed(): Date;
39
- set _lastUsed(value: string | number | Date);
30
+ export declare class Ident extends Model {
31
+ /** Composite primary key */
32
+ [WEBDA_PRIMARY_KEY]: readonly ["providerUid", "provider"];
33
+ [WEBDA_PRIMARY_KEY_SEPARATOR]: string;
34
+ /** Identifier on the provider (normalised email for "email") */
35
+ providerUid: string;
36
+ /** Provider name */
37
+ provider: string;
38
+ /** Owner */
39
+ get _user(): ModelLink<User>;
40
+ set _user(value: string | User | PrimaryKeyType<User> | ModelLink<User>);
41
+ /** Email asserted for this ident */
42
+ email?: string;
43
+ /** When the ident was verified */
44
+ get verifiedAt(): Date;
45
+ set verifiedAt(value: string | number | Date);
46
+ /** Last login with this ident */
47
+ get lastUsedAt(): Date;
48
+ set lastUsedAt(value: string | number | Date);
49
+ /** Email sending throttle state */
50
+ _throttle: IdentThrottle;
40
51
  /**
41
- * If the ident is validated
42
- */
43
- _failedLogin: number;
52
+ * Login attempts counted since the last successful login
53
+ *
54
+ * Top-level so stores can increment it atomically (nested-path atomic operations are not portable)
55
+ */
56
+ _loginAttempts: number;
57
+ /** Timestamp (ms) of the last counted login attempt */
58
+ _lastLoginAttemptAt?: number;
59
+ /** Provider profile */
60
+ __profile?: any;
61
+ /** Provider tokens, encrypted at rest (`await ident.tokens.get()`) */
62
+ tokens: EncryptedField<IdentTokens>;
63
+ /** @param data - initial data */
64
+ constructor(data?: Settable<Ident>);
44
65
  /**
45
- * If EmailIdent
46
- */
47
- _lastValidationEmail?: number;
66
+ * Build and validate a key
67
+ * @param providerUid - uid on the provider
68
+ * @param provider - provider name
69
+ * @returns the composite key
70
+ */
71
+ static key(providerUid: string, provider: string): {
72
+ providerUid: string;
73
+ provider: string;
74
+ };
48
75
  /**
49
- * When the ident was validated
50
- */
51
- get _validation(): Date;
52
- set _validation(value: string | number | Date);
76
+ * Parse a v3 storage key `"<providerUid>_<provider>"`, split on its LAST "_" (provider names never
77
+ * contain "_", provider uids such as emails may)
78
+ *
79
+ * Consulted by repositories for keys that are not `"<providerUid>:<provider>"` (see `WEBDA_LEGACY_UID`),
80
+ * so v3 records stay addressable and queryable until they are upgraded.
81
+ * @param uid - raw storage key
82
+ * @returns the key parts, or undefined when the key is not a v3 ident key
83
+ */
84
+ static parseLegacyUID(uid: string): {
85
+ providerUid: string;
86
+ provider: string;
87
+ } | undefined;
53
88
  /**
54
- * Email for this ident if it exist
89
+ * v3 storage key of a record stored before the composite key (v3 kept it as `uuid` in the body)
90
+ *
91
+ * Such an ident has no `providerUid`: it must be upgraded (`@webda/auth` `upgradeIdent`) before use.
92
+ * @returns the v3 key, or undefined for a current ident
55
93
  */
56
- email?: string;
94
+ getLegacyUID(): string | undefined;
57
95
  /**
58
- * Provider id
96
+ * Normalise an email for use as providerUid
97
+ * @param email - raw email
98
+ * @returns trimmed lowercase email
59
99
  */
60
- provider?: string;
100
+ static normalizeEmail(email: string): string;
61
101
  /**
62
- * Get the email for this ident
63
- * @returns the result string
102
+ * @returns the owner link
64
103
  */
65
- getEmail(): string;
104
+ getUser(): ModelLink<User>;
66
105
  /**
67
- * Get the provider type
68
- * @returns the result
106
+ * @param user - owner uuid or user
69
107
  */
70
- getType(): string;
108
+ setUser(user: string | User): void;
71
109
  /**
72
- * Set the provider type
73
- * @param type - the type to look up
110
+ * @returns the email of this ident
74
111
  */
75
- setType(type: any): void;
112
+ getEmail(): string | undefined;
76
113
  /**
77
- * Get the user who owns this ident
78
- * @returns the result
114
+ * @returns true when verified
79
115
  */
80
- getUser(): import("@webda/models").ModelLink<User>;
116
+ isVerified(): boolean;
81
117
  /**
82
- * Set the user who owns this ident
83
- * @param uuid - the unique identifier
118
+ * Only the owner may act on an ident
119
+ * @param ctx - operation context
120
+ * @param action - action name
121
+ * @returns true or a refusal reason
84
122
  */
85
- setUser(uuid: string | User): void;
123
+ canAct(ctx: IOperationContext, action: string): Promise<string | boolean>;
86
124
  /** Generated by @webda/content-mapper: include values held in WEBDA_STORAGE. */
87
125
  toJSON(): any;
126
+ protected __hydrateBehaviors(rawData?: any): void;
88
127
  }
89
128
  //# sourceMappingURL=ident.model.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ident.model.d.ts","sourceRoot":"","sources":["../../src/models/ident.model.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA8B,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,wDAAwD;AACxD,qBAAa,WAAW;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,KAAM,SAAQ,UAAU;IACnC;;OAEG;IACH,YAAY,IAAI,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,EAEjC;IACD;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,SAAS,EAAE,GAAG,CAAC;IACf;;OAEG;IACH,QAAQ,EAAE,WAAW,CAAC;IACtB;;OAEG;IACH,IAAI,SAAS,IAAI,IAAI,CAEpB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAE1C;IACD;;OAEG;IACH,YAAY,EAAE,MAAM,CAAK;IACzB;;OAEG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAK;IAClC;;OAEG;IACH,IAAI,WAAW,IAAI,IAAI,CAEtB;IACD,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAE5C;IACD;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,QAAQ,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,OAAO,WAEN;IAED;;;OAGG;IACH,OAAO,CAAC,IAAI,KAAA,QAEX;IAED;;;OAGG;IACH,OAAO,4CAEN;IAED;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,QAE1B;IAED,gFAAgF;IAChF,MAAM,IAAI,GAAG,CAOZ;CACF"}
1
+ {"version":3,"file":"ident.model.d.ts","sourceRoot":"","sources":["../../src/models/ident.model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAChH,OAAO,KAAK,UAAU,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,iGAAiG;AACjG,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,0DAA0D;AAC1D,qBAAa,YAAa,SAAQ,UAAU,CAAC,SAAS;IACpD,qCAAqC;IACrC,YAAY,OAAO,GAAE,MAAwB,EAE5C;CACF;AAID;;;GAGG;AACH,qBAAa,KAAM,SAAQ,KAAK;IAC9B,4BAA4B;IAC5B,CAAC,iBAAiB,CAAC,YAAI,aAAa,EAAE,UAAU,EAAW;IAE3D,CAAC,2BAA2B,CAAC,SAAO;IACpC,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY;IACZ,IAAI,KAAK,IAAI,SAAS,CAAC,IAAI,CAAC,CAE3B;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,EAUtE;IACD,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,IAAI,UAAU,IAAI,IAAI,CAErB;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAE3C;IACD,iCAAiC;IACjC,IAAI,UAAU,IAAI,IAAI,CAErB;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAE3C;IACD,mCAAmC;IACnC,SAAS,EAAE,aAAa,CAAM;IAC9B;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAK;IAC3B,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uBAAuB;IACvB,SAAS,CAAC,EAAE,GAAG,CAAC;IAChB,sEAAsE;IACtE,MAAM,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IAEpC,iCAAiC;IACjC,YAAY,IAAI,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,EAOjC;IAED;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAU3F;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CASxF;IAED;;;;;OAKG;IACH,YAAY,IAAI,MAAM,GAAG,SAAS,CAMjC;IAED;;;;OAIG;IACH,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3C;IAED;;OAEG;IACH,OAAO,IAAI,SAAS,CAAC,IAAI,CAAC,CAEzB;IAED;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAIjC;IAED;;OAEG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED;;OAEG;IACH,UAAU,IAAI,OAAO,CAEpB;IAED;;;;;OAKG;IACG,MAAM,CAAC,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,CAK9E;IAED,gFAAgF;IAChF,MAAM,IAAI,GAAG,CAQZ;IACD,SAAS,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,IAAI,CAiBhD;CACF"}
@@ -1,115 +1,207 @@
1
1
  import { WEBDA_STORAGE } from "@webda/models";
2
- import { OwnerModel } from "./ownermodel.model.js";
3
- /** OAuth tokens associated with an identity provider */
4
- export class IdentTokens {
5
- refresh;
6
- access;
2
+ import { Model, ModelLink, WEBDA_PRIMARY_KEY, WEBDA_PRIMARY_KEY_SEPARATOR } from "@webda/models";
3
+ import * as WebdaError from "../errors/errors.js";
4
+ import { User } from "./user.model.js";
5
+ import { EncryptedField } from "./encrypted.model.js";
6
+ /** Raised for malformed ident keys (400 INVALID_IDENT) */
7
+ export class InvalidIdent extends WebdaError.HttpError {
8
+ /** @param message - error message */
9
+ constructor(message = "Invalid ident") {
10
+ super(message, 400);
11
+ }
7
12
  }
13
+ const SEPARATOR = ":";
8
14
  /**
9
- * First basic model for Ident
10
- * @class
15
+ * An identity linked to a user: `<providerUid>:<provider>`
11
16
  * @WebdaModel
12
17
  */
13
- export class Ident extends OwnerModel {
14
- /** Create a new Ident
15
- * @param data - initial data
16
- */
17
- constructor(data) {
18
- super(data);
18
+ export class Ident extends Model {
19
+ /** Composite primary key */
20
+ [WEBDA_PRIMARY_KEY] = ["providerUid", "provider"];
21
+ // Must stay a string literal: the content mapper reads it textually
22
+ [WEBDA_PRIMARY_KEY_SEPARATOR] = ":";
23
+ /** Identifier on the provider (normalised email for "email") */
24
+ providerUid;
25
+ /** Provider name */
26
+ provider;
27
+ /** Owner */
28
+ get _user() {
29
+ return this[WEBDA_STORAGE]["_user"];
19
30
  }
20
- /**
21
- * Type of the ident
22
- */
23
- _type;
24
- /**
25
- * Uid on the provider
26
- */
27
- uid;
28
- /**
29
- * Provider profile
30
- */
31
- __profile;
32
- /**
33
- * Tokens for this ident
34
- */
35
- __tokens;
36
- /**
37
- * Last time the ident was used
38
- */
39
- get _lastUsed() {
40
- return this[WEBDA_STORAGE]["_lastUsed"] ?? undefined;
31
+ set _user(value) {
32
+ if (value instanceof ModelLink) {
33
+ this[WEBDA_STORAGE]["_user"] = value;
34
+ }
35
+ else if (value != null) {
36
+ const inst = this[WEBDA_STORAGE]["_user"] || new ModelLink(User);
37
+ inst.set(value);
38
+ this[WEBDA_STORAGE]["_user"] = inst;
39
+ }
40
+ else {
41
+ this[WEBDA_STORAGE]["_user"] = value;
42
+ }
43
+ }
44
+ /** Email asserted for this ident */
45
+ email;
46
+ /** When the ident was verified */
47
+ get verifiedAt() {
48
+ return this[WEBDA_STORAGE]["verifiedAt"];
41
49
  }
42
- set _lastUsed(value) {
43
- this[WEBDA_STORAGE]["_lastUsed"] = value !== undefined && value !== null ? new Date(value) : value;
50
+ set verifiedAt(value) {
51
+ this[WEBDA_STORAGE]["verifiedAt"] = value !== undefined && value !== null ? new Date(value) : value;
44
52
  }
53
+ /** Last login with this ident */
54
+ get lastUsedAt() {
55
+ return this[WEBDA_STORAGE]["lastUsedAt"];
56
+ }
57
+ set lastUsedAt(value) {
58
+ this[WEBDA_STORAGE]["lastUsedAt"] = value !== undefined && value !== null ? new Date(value) : value;
59
+ }
60
+ /** Email sending throttle state */
61
+ _throttle = {};
45
62
  /**
46
- * If the ident is validated
47
- */
48
- _failedLogin = 0;
49
- /**
50
- * If EmailIdent
51
- */
52
- _lastValidationEmail = 0;
63
+ * Login attempts counted since the last successful login
64
+ *
65
+ * Top-level so stores can increment it atomically (nested-path atomic operations are not portable)
66
+ */
67
+ _loginAttempts = 0;
68
+ /** Timestamp (ms) of the last counted login attempt */
69
+ _lastLoginAttemptAt;
70
+ /** Provider profile */
71
+ __profile;
72
+ /** Provider tokens, encrypted at rest (`await ident.tokens.get()`) */
73
+ tokens;
74
+ /** @param data - initial data */
75
+ constructor(data) {
76
+ super();
77
+ Object.assign(this, data);
78
+ // Raw stored data (or none) must become an EncryptedField behavior
79
+ if (!(this.tokens instanceof EncryptedField)) {
80
+ this.__hydrateBehaviors?.({ tokens: data?.tokens ?? {} });
81
+ }
82
+ }
53
83
  /**
54
- * When the ident was validated
55
- */
56
- get _validation() {
57
- return this[WEBDA_STORAGE]["_validation"];
84
+ * Build and validate a key
85
+ * @param providerUid - uid on the provider
86
+ * @param provider - provider name
87
+ * @returns the composite key
88
+ */
89
+ static key(providerUid, provider) {
90
+ for (const [name, v] of [
91
+ ["providerUid", providerUid],
92
+ ["provider", provider]
93
+ ]) {
94
+ if (typeof v !== "string" || v.length === 0 || v.includes(SEPARATOR)) {
95
+ throw new InvalidIdent(`${name} must be a non-empty string without '${SEPARATOR}'`);
96
+ }
97
+ }
98
+ return { providerUid, provider };
58
99
  }
59
- set _validation(value) {
60
- this[WEBDA_STORAGE]["_validation"] = value !== undefined && value !== null ? new Date(value) : value;
100
+ /**
101
+ * Parse a v3 storage key `"<providerUid>_<provider>"`, split on its LAST "_" (provider names never
102
+ * contain "_", provider uids such as emails may)
103
+ *
104
+ * Consulted by repositories for keys that are not `"<providerUid>:<provider>"` (see `WEBDA_LEGACY_UID`),
105
+ * so v3 records stay addressable and queryable until they are upgraded.
106
+ * @param uid - raw storage key
107
+ * @returns the key parts, or undefined when the key is not a v3 ident key
108
+ */
109
+ static parseLegacyUID(uid) {
110
+ if (typeof uid !== "string" || uid.includes(SEPARATOR)) {
111
+ return undefined;
112
+ }
113
+ const i = uid.lastIndexOf("_");
114
+ if (i <= 0 || i === uid.length - 1) {
115
+ return undefined;
116
+ }
117
+ return { providerUid: uid.substring(0, i), provider: uid.substring(i + 1) };
61
118
  }
62
119
  /**
63
- * Email for this ident if it exist
64
- */
65
- email;
120
+ * v3 storage key of a record stored before the composite key (v3 kept it as `uuid` in the body)
121
+ *
122
+ * Such an ident has no `providerUid`: it must be upgraded (`@webda/auth` `upgradeIdent`) before use.
123
+ * @returns the v3 key, or undefined for a current ident
124
+ */
125
+ getLegacyUID() {
126
+ const uuid = this.uuid;
127
+ if (this.providerUid || typeof uuid !== "string" || !Ident.parseLegacyUID(uuid)) {
128
+ return undefined;
129
+ }
130
+ return uuid;
131
+ }
66
132
  /**
67
- * Provider id
133
+ * Normalise an email for use as providerUid
134
+ * @param email - raw email
135
+ * @returns trimmed lowercase email
68
136
  */
69
- provider;
137
+ static normalizeEmail(email) {
138
+ return `${email ?? ""}`.trim().toLowerCase();
139
+ }
70
140
  /**
71
- * Get the email for this ident
72
- * @returns the result string
141
+ * @returns the owner link
73
142
  */
74
- getEmail() {
75
- return this.email;
143
+ getUser() {
144
+ return this._user;
76
145
  }
77
146
  /**
78
- * Get the provider type
79
- * @returns the result
147
+ * @param user - owner uuid or user
80
148
  */
81
- getType() {
82
- return this._type;
149
+ setUser(user) {
150
+ const uuid = typeof user === "string" ? user : user.getUUID();
151
+ this._user ??= new ModelLink(User).set(uuid);
152
+ this._user.set(uuid);
83
153
  }
84
154
  /**
85
- * Set the provider type
86
- * @param type - the type to look up
155
+ * @returns the email of this ident
87
156
  */
88
- setType(type) {
89
- this._type = type;
157
+ getEmail() {
158
+ return this.email;
90
159
  }
91
160
  /**
92
- * Get the user who owns this ident
93
- * @returns the result
161
+ * @returns true when verified
94
162
  */
95
- getUser() {
96
- return this.getOwner();
163
+ isVerified() {
164
+ return this.verifiedAt !== undefined && this.verifiedAt !== null;
97
165
  }
98
166
  /**
99
- * Set the user who owns this ident
100
- * @param uuid - the unique identifier
101
- */
102
- setUser(uuid) {
103
- this.setOwner(typeof uuid === "string" ? uuid : uuid.getPrimaryKey());
167
+ * Only the owner may act on an ident
168
+ * @param ctx - operation context
169
+ * @param action - action name
170
+ * @returns true or a refusal reason
171
+ */
172
+ async canAct(ctx, action) {
173
+ if (!ctx.getCurrentUserId() || this._user?.toString() !== ctx.getCurrentUserId()) {
174
+ return "You can't act on this ident";
175
+ }
176
+ return true;
104
177
  }
105
178
  /** Generated by @webda/content-mapper: include values held in WEBDA_STORAGE. */
106
179
  toJSON() {
107
- const result = super.toJSON ? super.toJSON() : {};
180
+ const parent = super.toJSON ? super.toJSON() : {};
181
+ const result = parent === this ? {} : parent;
108
182
  for (const key of Object.keys(this)) {
109
183
  result[key] = this[key];
110
184
  }
111
185
  Object.assign(result, this[WEBDA_STORAGE]);
112
186
  return result;
113
187
  }
188
+ __hydrateBehaviors(rawData) {
189
+ {
190
+ let v = this.tokens;
191
+ if (rawData !== undefined && rawData !== null && rawData["tokens"] !== undefined) {
192
+ v = rawData["tokens"];
193
+ }
194
+ if (!(v instanceof EncryptedField)) {
195
+ const inst = new EncryptedField();
196
+ if (v !== undefined && v !== null) {
197
+ Object.assign(inst, v);
198
+ }
199
+ v = inst;
200
+ }
201
+ v[WEBDA_STORAGE] = v[WEBDA_STORAGE] || {};
202
+ v[WEBDA_STORAGE]["__parent__"] = { instance: this, attribute: "tokens" };
203
+ this.tokens = v;
204
+ }
205
+ }
114
206
  }
115
207
  //# sourceMappingURL=ident.model.js.map