firstly 0.0.1 → 0.0.3

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 (156) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +18 -0
  3. package/README.md +12 -0
  4. package/esm/KitBaseEnum.d.ts +35 -0
  5. package/esm/KitBaseEnum.js +32 -0
  6. package/esm/KitEntity.d.ts +2 -0
  7. package/esm/KitEntity.js +24 -0
  8. package/esm/KitFields.d.ts +10 -0
  9. package/esm/KitFields.js +196 -0
  10. package/esm/ROUTES.d.ts +88 -0
  11. package/esm/ROUTES.js +98 -0
  12. package/esm/SqlDatabase/LogToConsoleCustom.d.ts +1 -0
  13. package/esm/SqlDatabase/LogToConsoleCustom.js +102 -0
  14. package/esm/api/index.d.ts +42 -0
  15. package/esm/api/index.js +97 -0
  16. package/esm/auth/Adapter.d.ts +10 -0
  17. package/esm/auth/Adapter.js +54 -0
  18. package/esm/auth/AuthController.d.ts +59 -0
  19. package/esm/auth/AuthController.js +434 -0
  20. package/esm/auth/Entities.d.ts +39 -0
  21. package/esm/auth/Entities.js +154 -0
  22. package/esm/auth/RoleController.d.ts +14 -0
  23. package/esm/auth/RoleController.js +57 -0
  24. package/esm/auth/helper.d.ts +1 -0
  25. package/esm/auth/helper.js +7 -0
  26. package/esm/auth/index.d.ts +153 -0
  27. package/esm/auth/index.js +280 -0
  28. package/esm/auth/providers/github.d.ts +25 -0
  29. package/esm/auth/providers/github.js +51 -0
  30. package/esm/auth/providers/index.d.ts +3 -0
  31. package/esm/auth/providers/index.js +26 -0
  32. package/esm/auth/providers/strava.d.ts +25 -0
  33. package/esm/auth/providers/strava.js +51 -0
  34. package/esm/auth/static/assets/Page-BMFREPjF.d.ts +5 -0
  35. package/esm/auth/static/assets/Page-BMFREPjF.js +18 -0
  36. package/esm/auth/static/assets/Page-BMOLAIFx.d.ts +5 -0
  37. package/esm/auth/static/assets/Page-BMOLAIFx.js +1 -0
  38. package/esm/auth/static/assets/Page-BwHye0GW.d.ts +5 -0
  39. package/esm/auth/static/assets/Page-BwHye0GW.js +1 -0
  40. package/esm/auth/static/assets/Page-gV58jf2r.css +1 -0
  41. package/esm/auth/static/assets/index-CKmKKRRL.d.ts +53 -0
  42. package/esm/auth/static/assets/index-CKmKKRRL.js +2 -0
  43. package/esm/auth/static/assets/index-R27C_TlP.css +4 -0
  44. package/esm/auth/static/favicon.svg +79 -0
  45. package/esm/auth/static/index.html +14 -0
  46. package/esm/auth/types.d.ts +33 -0
  47. package/esm/auth/types.js +1 -0
  48. package/esm/bin/cmd.d.ts +1 -0
  49. package/esm/bin/cmd.js +418 -0
  50. package/esm/changeLog/index.d.ts +55 -0
  51. package/esm/changeLog/index.js +179 -0
  52. package/esm/cron/index.d.ts +60 -0
  53. package/esm/cron/index.js +102 -0
  54. package/esm/feedback/FeedbackController.d.ts +30 -0
  55. package/esm/feedback/FeedbackController.js +313 -0
  56. package/esm/feedback/index.d.ts +18 -0
  57. package/esm/feedback/index.js +14 -0
  58. package/esm/feedback/ui/DialogIssue.svelte +102 -0
  59. package/esm/feedback/ui/DialogIssue.svelte.d.ts +20 -0
  60. package/esm/feedback/ui/DialogIssues.svelte +91 -0
  61. package/esm/feedback/ui/DialogIssues.svelte.d.ts +20 -0
  62. package/esm/feedback/ui/DialogMilestones.svelte +38 -0
  63. package/esm/feedback/ui/DialogMilestones.svelte.d.ts +18 -0
  64. package/esm/feedback/ui/Feedback.svelte +12 -0
  65. package/esm/feedback/ui/Feedback.svelte.d.ts +16 -0
  66. package/esm/formats/dates.d.ts +18 -0
  67. package/esm/formats/dates.js +35 -0
  68. package/esm/formats/index.d.ts +4 -0
  69. package/esm/formats/index.js +3 -0
  70. package/esm/formats/numbers.d.ts +4 -0
  71. package/esm/formats/numbers.js +34 -0
  72. package/esm/formats/strings.d.ts +11 -0
  73. package/esm/formats/strings.js +109 -0
  74. package/esm/handle/index.d.ts +7 -0
  75. package/esm/handle/index.js +40 -0
  76. package/esm/helper.d.ts +50 -0
  77. package/esm/helper.js +118 -0
  78. package/esm/index.d.ts +103 -0
  79. package/esm/index.js +42 -0
  80. package/esm/kitCellsBuildor.d.ts +45 -0
  81. package/esm/kitCellsBuildor.js +105 -0
  82. package/esm/kitStoreItem.d.ts +28 -0
  83. package/esm/kitStoreItem.js +170 -0
  84. package/esm/kitStoreList.d.ts +33 -0
  85. package/esm/kitStoreList.js +98 -0
  86. package/esm/mail/index.d.ts +11 -0
  87. package/esm/mail/index.js +51 -0
  88. package/esm/theme.d.ts +4 -0
  89. package/esm/theme.js +4 -0
  90. package/esm/ui/Button.svelte +102 -0
  91. package/esm/ui/Button.svelte.d.ts +27 -0
  92. package/esm/ui/Clipboardable.svelte +19 -0
  93. package/esm/ui/Clipboardable.svelte.d.ts +25 -0
  94. package/esm/ui/Field.svelte +288 -0
  95. package/esm/ui/Field.svelte.d.ts +29 -0
  96. package/esm/ui/FieldGroup.svelte +91 -0
  97. package/esm/ui/FieldGroup.svelte.d.ts +30 -0
  98. package/esm/ui/Grid.svelte +246 -0
  99. package/esm/ui/Grid.svelte.d.ts +46 -0
  100. package/esm/ui/GridLoading.svelte +32 -0
  101. package/esm/ui/GridLoading.svelte.d.ts +20 -0
  102. package/esm/ui/GridPaginate.svelte +66 -0
  103. package/esm/ui/GridPaginate.svelte.d.ts +22 -0
  104. package/esm/ui/Icon.svelte +86 -0
  105. package/esm/ui/Icon.svelte.d.ts +46 -0
  106. package/esm/ui/LibIcon.d.ts +23 -0
  107. package/esm/ui/LibIcon.js +28 -0
  108. package/esm/ui/Loading.svelte +11 -0
  109. package/esm/ui/Loading.svelte.d.ts +20 -0
  110. package/esm/ui/Tooltip.svelte +42 -0
  111. package/esm/ui/Tooltip.svelte.d.ts +22 -0
  112. package/esm/ui/dialog/DialogForm.svelte +70 -0
  113. package/esm/ui/dialog/DialogForm.svelte.d.ts +19 -0
  114. package/esm/ui/dialog/DialogManagement.svelte +87 -0
  115. package/esm/ui/dialog/DialogManagement.svelte.d.ts +25 -0
  116. package/esm/ui/dialog/DialogPrimitive.svelte +89 -0
  117. package/esm/ui/dialog/DialogPrimitive.svelte.d.ts +28 -0
  118. package/esm/ui/dialog/FormEditAction.svelte +54 -0
  119. package/esm/ui/dialog/FormEditAction.svelte.d.ts +24 -0
  120. package/esm/ui/dialog/dialog.d.ts +51 -0
  121. package/esm/ui/dialog/dialog.js +98 -0
  122. package/esm/ui/index.d.ts +5 -0
  123. package/esm/ui/index.js +19 -0
  124. package/esm/ui/internals/FieldContainer.svelte +22 -0
  125. package/esm/ui/internals/FieldContainer.svelte.d.ts +30 -0
  126. package/esm/ui/internals/Input.svelte +98 -0
  127. package/esm/ui/internals/Input.svelte.d.ts +35 -0
  128. package/esm/ui/internals/Textarea.svelte +61 -0
  129. package/esm/ui/internals/Textarea.svelte.d.ts +30 -0
  130. package/esm/ui/internals/select/MultiSelectMelt.svelte +217 -0
  131. package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +30 -0
  132. package/esm/ui/internals/select/SelectMelt.svelte +238 -0
  133. package/esm/ui/internals/select/SelectMelt.svelte.d.ts +35 -0
  134. package/esm/ui/internals/select/SelectRadio.svelte +37 -0
  135. package/esm/ui/internals/select/SelectRadio.svelte.d.ts +25 -0
  136. package/esm/ui/link/Link.svelte +28 -0
  137. package/esm/ui/link/Link.svelte.d.ts +25 -0
  138. package/esm/ui/link/LinkPlus.svelte +44 -0
  139. package/esm/ui/link/LinkPlus.svelte.d.ts +21 -0
  140. package/esm/utils/tailwind.d.ts +2 -0
  141. package/esm/utils/tailwind.js +3 -0
  142. package/esm/utils/transition.d.ts +10 -0
  143. package/esm/utils/transition.js +33 -0
  144. package/esm/utils/types.d.ts +17 -0
  145. package/esm/utils/types.js +17 -0
  146. package/esm/virtual/Customer.d.ts +4 -0
  147. package/esm/virtual/Customer.js +24 -0
  148. package/esm/virtual/FilterEntity.d.ts +7 -0
  149. package/esm/virtual/FilterEntity.js +34 -0
  150. package/esm/virtual/StateDemoEnum.d.ts +9 -0
  151. package/esm/virtual/StateDemoEnum.js +42 -0
  152. package/esm/virtual/UIEntity.d.ts +16 -0
  153. package/esm/virtual/UIEntity.js +84 -0
  154. package/esm/vite/index.d.ts +8 -0
  155. package/esm/vite/index.js +47 -0
  156. package/package.json +94 -10
@@ -0,0 +1,434 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { generateCodeVerifier, generateState } from 'arctic';
8
+ import { DEV } from 'esm-env';
9
+ import { generateId } from 'lucia';
10
+ import { createDate, TimeSpan } from 'oslo';
11
+ import { BackendMethod, remult } from 'remult';
12
+ import { green, yellow } from '@kitql/helpers';
13
+ import { AUTH_OPTIONS, getSafeOptions, logAuth, lucia } from '.';
14
+ import { sendMail } from '../mail';
15
+ import { AuthProvider } from './Entities.js';
16
+ import { createSession } from './helper';
17
+ import { mergeRoles } from './RoleController';
18
+ async function getArgon() {
19
+ const { Argon2id } = await import('oslo/password');
20
+ return new Argon2id({
21
+ ...AUTH_OPTIONS.providers?.password?.argon2Settings,
22
+ });
23
+ }
24
+ async function passwordVerify(hash, password) {
25
+ const argon = await getArgon();
26
+ return await argon.verify(hash, password);
27
+ }
28
+ async function passwordHash(password) {
29
+ const argon = await getArgon();
30
+ return await argon.hash(password);
31
+ }
32
+ function checkPassword(password) {
33
+ if (typeof password !== 'string' || password.length < 6 || password.length > 255) {
34
+ throw Error('Invalid password');
35
+ }
36
+ }
37
+ export class AuthController {
38
+ /**
39
+ * Sign out the current user
40
+ */
41
+ static async signOut() {
42
+ if (remult.user?.session.id) {
43
+ await lucia.invalidateSession(remult.user?.session.id);
44
+ }
45
+ // Lucia is advertising for createBlankSessionCookie (and not delete Cookie)
46
+ // remult.context.deleteCookie(lucia.sessionCookieName, { path: '/' })
47
+ const sessionCookie = lucia.createBlankSessionCookie();
48
+ remult.context.setCookie(sessionCookie.name, sessionCookie.value, {
49
+ path: '/',
50
+ ...sessionCookie.attributes,
51
+ });
52
+ }
53
+ /**
54
+ * Sign in with a demo account
55
+ * _(The easiest way to demo & test your application)_
56
+ */
57
+ static async signInDemo(name) {
58
+ const accounts = AUTH_OPTIONS.providers?.demo ?? [];
59
+ if (accounts.length === 0) {
60
+ throw new Error(`Demo accounts are not enabled!`);
61
+ }
62
+ const account = accounts.find((a) => a.name === name);
63
+ if (!account) {
64
+ throw new Error(`${name} not found as demo account!`);
65
+ }
66
+ const oSafe = getSafeOptions();
67
+ let user = await remult.repo(oSafe.User).findFirst({ name });
68
+ if (!user) {
69
+ user = remult.repo(oSafe.User).create();
70
+ }
71
+ user.name = name;
72
+ const r = mergeRoles(user.roles, account.roles);
73
+ user.roles = r.roles;
74
+ await remult.repo(oSafe.User).save(user);
75
+ await createSession(user.id);
76
+ return "You're in with demo account!";
77
+ }
78
+ /**
79
+ * This is for login / password authentication SignUp
80
+ * _(The first param `name` can be "anything")_
81
+ */
82
+ static async invite(email) {
83
+ const oSafe = getSafeOptions();
84
+ const existingUser = await remult.repo(oSafe.User).findOne({ where: { name: email } });
85
+ if (existingUser) {
86
+ // throw Error("Already invited !")
87
+ }
88
+ else {
89
+ const user = await remult.repo(oSafe.User).insert({
90
+ name: email,
91
+ });
92
+ const url = `${remult.context.url.origin}`;
93
+ if (AUTH_OPTIONS?.invitationSend) {
94
+ await AUTH_OPTIONS?.invitationSend({ email, url });
95
+ logAuth.success(`Done with custom ${green('invitationSend')} (${yellow(url)})`);
96
+ return 'Mail sent !';
97
+ }
98
+ else {
99
+ await sendMail('invite', {
100
+ to: email,
101
+ subject: 'Invitation',
102
+ text: `You were invited here: ${url}`,
103
+ html: `You were invited <a href="${url}">here</a>`,
104
+ });
105
+ logAuth.success(`Done with ${green('sendMail')} (${url})`);
106
+ return 'Demo Mail sent !';
107
+ }
108
+ }
109
+ return 'ok';
110
+ }
111
+ /**
112
+ * This is for login / password authentication SignUp
113
+ * _(The first param `email` can be "anything")_
114
+ */
115
+ static async signUpPassword(email, password) {
116
+ const oSafe = getSafeOptions();
117
+ if (!oSafe.signUp) {
118
+ throw Error("You can't signup by yourself! Contact the administrator.");
119
+ }
120
+ if (!oSafe.password_enabled) {
121
+ throw Error('Password is not enabled!');
122
+ }
123
+ const existingUser = await remult.repo(oSafe.User).findOne({ where: { name: email } });
124
+ if (existingUser) {
125
+ throw Error("You can't signup twice !");
126
+ }
127
+ checkPassword(password);
128
+ const user = await remult.repo(oSafe.User).insert({
129
+ name: email,
130
+ });
131
+ const token = generateId(40);
132
+ await remult.repo(oSafe.Account).insert({
133
+ provider: AuthProvider.PASSWORD.id,
134
+ providerUserId: email,
135
+ userId: user.id,
136
+ hashPassword: await passwordHash(password),
137
+ token: oSafe.verifiedMethod === 'auto' ? undefined : token,
138
+ expiresAt: oSafe.verifiedMethod === 'auto'
139
+ ? undefined
140
+ : createDate(new TimeSpan(AUTH_OPTIONS.providers?.password?.verifyMailExpiresIn ?? 5 * 60, 's')),
141
+ lastVerifiedAt: oSafe.verifiedMethod === 'auto' ? new Date() : undefined,
142
+ });
143
+ if (oSafe.verifiedMethod === 'auto') {
144
+ await createSession(user.id);
145
+ }
146
+ else {
147
+ const url = `${remult.context.url.origin}${oSafe.firstlyData.props.ui.providers.password.paths.verify_email}?token=${token}`;
148
+ if (AUTH_OPTIONS.providers?.password?.verifyMailSend) {
149
+ await AUTH_OPTIONS.providers?.password.verifyMailSend({ email, url });
150
+ logAuth.success(`Done with custom ${green('verifyMailSend')} (${yellow(url)})`);
151
+ }
152
+ else {
153
+ await sendMail('signUpPassword', {
154
+ to: email,
155
+ subject: 'Wecome!',
156
+ text: `You can validate your account here: ${url}`,
157
+ html: `You can validate your account <a href="${url}">here</a>`,
158
+ });
159
+ logAuth.success(`Done with ${green('sendMail')} (${url})`);
160
+ }
161
+ }
162
+ return 'ok';
163
+ }
164
+ /**
165
+ * This is for login / password authentication SignIn
166
+ * _(The first param `email` can be "anything")_
167
+ */
168
+ static async signInPassword(email, password) {
169
+ const oSafe = getSafeOptions();
170
+ if (!oSafe.password_enabled) {
171
+ throw Error('Password is not enabled!');
172
+ }
173
+ const existingUser = await remult
174
+ .repo(oSafe.User)
175
+ .findOne({ where: { name: email }, include: { accounts: true } });
176
+ const accountPassword = existingUser?.accounts.find((c) => c.provider === AuthProvider.PASSWORD.id);
177
+ if (accountPassword) {
178
+ const validPassword = await passwordVerify(accountPassword?.hashPassword ?? '', password ?? '');
179
+ if (validPassword) {
180
+ await createSession(existingUser.id);
181
+ return 'ok';
182
+ }
183
+ throw Error('Incorrect username or password');
184
+ }
185
+ throw Error('Incorrect username or password.');
186
+ }
187
+ /**
188
+ * Forgot your password ? Send a mail to reset it.
189
+ */
190
+ static async forgotPassword(email) {
191
+ const oSafe = getSafeOptions();
192
+ if (!oSafe.password_enabled) {
193
+ throw Error('Password is not enabled!');
194
+ }
195
+ const u = await remult.repo(getSafeOptions().User).findFirst({ name: email });
196
+ if (u) {
197
+ let authAccount = await remult.repo(oSafe.Account).findFirst({
198
+ userId: u.id,
199
+ });
200
+ if (!authAccount) {
201
+ authAccount = remult.repo(oSafe.Account).create();
202
+ authAccount.userId = u.id;
203
+ authAccount.provider = AuthProvider.PASSWORD.id;
204
+ authAccount.providerUserId = email;
205
+ }
206
+ const token = generateId(40);
207
+ authAccount.token = token;
208
+ authAccount.expiresAt = createDate(new TimeSpan(AUTH_OPTIONS.providers?.password?.resetPasswordExpiresIn ?? 5 * 60, 's'));
209
+ await remult.repo(oSafe.Account).save(authAccount);
210
+ const url = `${remult.context.url.origin}${oSafe.firstlyData.props.ui.providers.password.paths.reset_password}?token=${token}`;
211
+ if (AUTH_OPTIONS.providers?.password?.resetPasswordSend) {
212
+ await AUTH_OPTIONS.providers?.password.resetPasswordSend({ email, url });
213
+ logAuth.success(`Done with custom ${green('resetPasswordSend')} (${yellow(url)})`);
214
+ return 'Mail sent !';
215
+ }
216
+ else {
217
+ await sendMail('forgotPassword', {
218
+ to: email,
219
+ subject: 'Reset your password',
220
+ text: `You can reset your password here: ${url}`,
221
+ html: `You can reset your password <a href="${url}">here</a>`,
222
+ });
223
+ logAuth.success(`Done with ${green('sendMail')} (${url})`);
224
+ return 'Demo Mail sent !';
225
+ }
226
+ }
227
+ throw new Error("Une erreur est survenue, contacte l'administrateur!");
228
+ }
229
+ /**
230
+ * Reset your password with a token
231
+ */
232
+ static async resetPassword(token, password) {
233
+ const oSafe = getSafeOptions();
234
+ if (!oSafe.password_enabled) {
235
+ throw Error('Password is not enabled!');
236
+ }
237
+ const account = await remult
238
+ .repo(oSafe.Account)
239
+ .findFirst({ token, provider: AuthProvider.PASSWORD.id });
240
+ if (!account) {
241
+ throw new Error('Invalid token');
242
+ }
243
+ if (account.expiresAt && account.expiresAt < new Date()) {
244
+ throw new Error('token expired');
245
+ }
246
+ checkPassword(password);
247
+ await lucia.invalidateUserSessions(account.userId);
248
+ // update elements
249
+ account.hashPassword = await passwordHash(password);
250
+ account.token = undefined;
251
+ account.expiresAt = undefined;
252
+ account.lastVerifiedAt = new Date();
253
+ await remult.repo(oSafe.Account).save(account);
254
+ await createSession(account.userId);
255
+ return 'reseted';
256
+ }
257
+ /** OTP */
258
+ static async signInOTP(email) {
259
+ const oSafe = getSafeOptions();
260
+ if (!oSafe.otp_enabled) {
261
+ throw new Error(`OPT is not enabled!`);
262
+ }
263
+ if (AUTH_OPTIONS.providers?.otp?.send) {
264
+ const { createTOTPKeyURI } = await import('oslo/otp');
265
+ const { encodeHex } = await import('oslo/encoding');
266
+ const { TOTPController } = await import('oslo/otp');
267
+ const secret = crypto.getRandomValues(new Uint8Array(20));
268
+ const otp = await new TOTPController({
269
+ period: new TimeSpan(AUTH_OPTIONS.providers?.otp.expiresIn ?? 30, 's'),
270
+ digits: AUTH_OPTIONS.providers?.otp.digits ?? 6,
271
+ }).generate(secret);
272
+ const secretEncoded = encodeHex(secret);
273
+ const issuer = AUTH_OPTIONS.providers.otp.issuer ?? 'firstly';
274
+ const uri = createTOTPKeyURI(issuer, email, secret);
275
+ const oSafe = getSafeOptions();
276
+ let user = await remult.repo(oSafe.User).findFirst({ name: email });
277
+ if (!user) {
278
+ user = remult.repo(oSafe.User).create();
279
+ }
280
+ user.name = email;
281
+ user = await remult.repo(oSafe.User).save(user);
282
+ let account = await remult
283
+ .repo(oSafe.Account)
284
+ .findFirst({ userId: user.id, provider: AuthProvider.OTP.id });
285
+ if (!account) {
286
+ account = remult.repo(oSafe.Account).create();
287
+ }
288
+ account.userId = user.id;
289
+ account.provider = AuthProvider.OTP.id;
290
+ account.token = otp;
291
+ account.hashPassword = secretEncoded;
292
+ await remult.repo(oSafe.Account).save(account);
293
+ await AUTH_OPTIONS.providers.otp?.send({ name: email, otp, uri });
294
+ logAuth.success(`name: ${yellow(email)}, otp: ${yellow(otp)}, uri: ${yellow(uri)}`);
295
+ return 'Mail sent !';
296
+ }
297
+ else {
298
+ logAuth.error(`You need to provide a otp.send hook in the auth options!`);
299
+ }
300
+ return 'Hum, something went wrong !';
301
+ }
302
+ /**
303
+ * Verify the OTP code
304
+ */
305
+ static async verifyOtp(email, otp) {
306
+ const oSafe = getSafeOptions();
307
+ const accounts = await remult.repo(oSafe.Account).find({
308
+ where: { token: String(otp), provider: AuthProvider.OTP.id },
309
+ });
310
+ if (accounts.length === 0) {
311
+ throw new Error('Invalid otp');
312
+ }
313
+ const account = accounts[0];
314
+ const user = await remult.repo(oSafe.User).findId(account.userId);
315
+ if (user.name !== email) {
316
+ throw new Error('Invalid otp.');
317
+ }
318
+ const { decodeHex } = await import('oslo/encoding');
319
+ const { TOTPController } = await import('oslo/otp');
320
+ const secretDecoded = decodeHex(account.hashPassword ?? '');
321
+ const validOTP = await new TOTPController().verify(String(otp), secretDecoded);
322
+ if (!validOTP) {
323
+ throw new Error('Invalid otp!');
324
+ }
325
+ await lucia.invalidateUserSessions(account.userId);
326
+ // update elements
327
+ account.hashPassword = undefined;
328
+ account.token = undefined;
329
+ account.expiresAt = undefined;
330
+ await remult.repo(oSafe.Account).save(account);
331
+ await createSession(account.userId);
332
+ return 'verified';
333
+ }
334
+ /** OAUTH */
335
+ /**
336
+ * The the url to redirect to for the OAuth provider
337
+ * @param provider Has to mach one of `AUTH_OPTIONS.providers.oAuths` your configured
338
+ *
339
+ * To be used like this for example:
340
+ * ```
341
+ * const url = await AuthController.signInOAuthGetUrl('github')
342
+ * window.location.href = url
343
+ * ```
344
+ *
345
+ * _(popup example should work too, and a nice example/componant would be appreciated)_
346
+ */
347
+ static async signInOAuthGetUrl(o) {
348
+ const selectedOAuth = AUTH_OPTIONS.providers?.oAuths?.find((c) => c.name === o.provider);
349
+ if (selectedOAuth) {
350
+ const state = generateState();
351
+ try {
352
+ const arcticProvider = selectedOAuth.getArcticProvider();
353
+ const args = [state];
354
+ if (selectedOAuth.isPKCE) {
355
+ const codeVerifier = generateCodeVerifier();
356
+ args.push(codeVerifier);
357
+ // store code verifier as cookie
358
+ remult.context.setCookie('code_verifier', codeVerifier, {
359
+ secure: true, // set to false in localhost
360
+ path: '/',
361
+ httpOnly: true,
362
+ maxAge: 60 * 10, // 10 min
363
+ });
364
+ }
365
+ if (o.options) {
366
+ args.push(o.options);
367
+ }
368
+ else {
369
+ if (selectedOAuth.authorizationURLOptions) {
370
+ args.push(selectedOAuth.authorizationURLOptions());
371
+ }
372
+ }
373
+ // @ts-ignore
374
+ const url = await arcticProvider.createAuthorizationURL(...args);
375
+ if (!url) {
376
+ throw new Error('No url returned');
377
+ }
378
+ remult.context.setCookie(`${o.provider}_oauth_state`, state, {
379
+ path: '/',
380
+ secure: !DEV,
381
+ httpOnly: true,
382
+ maxAge: 60 * 10,
383
+ sameSite: 'lax',
384
+ });
385
+ if (o.redirect) {
386
+ remult.context.setCookie(`remult_redirect`, o.redirect, {
387
+ path: '/',
388
+ secure: !DEV,
389
+ httpOnly: true,
390
+ maxAge: 60 * 10,
391
+ sameSite: 'lax',
392
+ });
393
+ }
394
+ return url.toString();
395
+ }
396
+ catch (error) {
397
+ // display error for the server only
398
+ logAuth.error(error);
399
+ throw new Error(`${o.provider} not well configured!`);
400
+ }
401
+ }
402
+ throw new Error(`${o.provider} is not configured!`);
403
+ }
404
+ }
405
+ __decorate([
406
+ BackendMethod({ allowed: true })
407
+ ], AuthController, "signOut", null);
408
+ __decorate([
409
+ BackendMethod({ allowed: true })
410
+ ], AuthController, "signInDemo", null);
411
+ __decorate([
412
+ BackendMethod({ allowed: false })
413
+ ], AuthController, "invite", null);
414
+ __decorate([
415
+ BackendMethod({ allowed: true })
416
+ ], AuthController, "signUpPassword", null);
417
+ __decorate([
418
+ BackendMethod({ allowed: true })
419
+ ], AuthController, "signInPassword", null);
420
+ __decorate([
421
+ BackendMethod({ allowed: true })
422
+ ], AuthController, "forgotPassword", null);
423
+ __decorate([
424
+ BackendMethod({ allowed: true })
425
+ ], AuthController, "resetPassword", null);
426
+ __decorate([
427
+ BackendMethod({ allowed: true })
428
+ ], AuthController, "signInOTP", null);
429
+ __decorate([
430
+ BackendMethod({ allowed: true })
431
+ ], AuthController, "verifyOtp", null);
432
+ __decorate([
433
+ BackendMethod({ allowed: true })
434
+ ], AuthController, "signInOAuthGetUrl", null);
@@ -0,0 +1,39 @@
1
+ import { KitBaseEnum } from '../';
2
+ import type { KitBaseEnumOptions } from '../';
3
+ export declare const KitAuthRole: {
4
+ readonly Admin: "KitAuthAdmin";
5
+ };
6
+ export declare class KitAuthUser {
7
+ id: string;
8
+ createdAt: Date;
9
+ updatedAt?: Date;
10
+ name: string;
11
+ roles: string[];
12
+ accounts: KitAuthAccount[];
13
+ sessions: KitAuthUserSession[];
14
+ }
15
+ export declare class KitAuthAccount {
16
+ createdAt: Date;
17
+ updatedAt?: Date;
18
+ userId: string;
19
+ user: KitAuthUser;
20
+ provider: string;
21
+ providerUserId: string;
22
+ hashPassword?: string;
23
+ token?: string;
24
+ expiresAt?: Date;
25
+ lastVerifiedAt?: Date;
26
+ }
27
+ export declare class KitAuthUserSession {
28
+ id: string;
29
+ expiresAt: Date;
30
+ userId: string;
31
+ user: KitAuthUser;
32
+ }
33
+ export declare class AuthProvider extends KitBaseEnum {
34
+ static DEMO: AuthProvider;
35
+ static PASSWORD: AuthProvider;
36
+ static OTP: AuthProvider;
37
+ static OAUTH: AuthProvider;
38
+ constructor(id: string, o?: KitBaseEnumOptions);
39
+ }
@@ -0,0 +1,154 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var AuthProvider_1;
8
+ import { Entity, Fields, Relations, Validators, ValueListFieldType } from 'remult';
9
+ import { KitBaseEnum, KitRole } from '../';
10
+ export const KitAuthRole = {
11
+ Admin: 'KitAuthAdmin',
12
+ };
13
+ let KitAuthUser = class KitAuthUser {
14
+ id;
15
+ createdAt;
16
+ updatedAt;
17
+ // @Fields.string<KitAuthUser>({
18
+ name;
19
+ roles = [];
20
+ accounts;
21
+ sessions;
22
+ };
23
+ __decorate([
24
+ Fields.cuid()
25
+ ], KitAuthUser.prototype, "id", void 0);
26
+ __decorate([
27
+ Fields.createdAt()
28
+ ], KitAuthUser.prototype, "createdAt", void 0);
29
+ __decorate([
30
+ Fields.updatedAt()
31
+ ], KitAuthUser.prototype, "updatedAt", void 0);
32
+ __decorate([
33
+ Fields.string({
34
+ validate: [
35
+ Validators.unique(),
36
+ (e) => {
37
+ if (e.name.length < 2)
38
+ throw 'Must be at least 2 characters long';
39
+ },
40
+ ],
41
+ })
42
+ ], KitAuthUser.prototype, "name", void 0);
43
+ __decorate([
44
+ Fields.object({
45
+ valueConverter: {
46
+ toDb: (x) => (x ? x.join(',') : undefined),
47
+ fromDb: (x) => (x ? x.split(',') : undefined),
48
+ },
49
+ })
50
+ ], KitAuthUser.prototype, "roles", void 0);
51
+ __decorate([
52
+ Relations.toMany(() => KitAuthAccount, 'userId')
53
+ ], KitAuthUser.prototype, "accounts", void 0);
54
+ __decorate([
55
+ Relations.toMany(() => KitAuthUserSession, 'userId')
56
+ ], KitAuthUser.prototype, "sessions", void 0);
57
+ KitAuthUser = __decorate([
58
+ Entity('kit_auth_user', {
59
+ allowApiCrud: [KitAuthRole.Admin, KitRole.Admin],
60
+ dbName: 'auth.kit_auth_user',
61
+ })
62
+ ], KitAuthUser);
63
+ export { KitAuthUser };
64
+ let KitAuthAccount = class KitAuthAccount {
65
+ createdAt;
66
+ updatedAt;
67
+ userId;
68
+ user;
69
+ provider = AuthProvider.PASSWORD.id;
70
+ providerUserId = '';
71
+ hashPassword;
72
+ token;
73
+ expiresAt;
74
+ lastVerifiedAt;
75
+ };
76
+ __decorate([
77
+ Fields.createdAt()
78
+ ], KitAuthAccount.prototype, "createdAt", void 0);
79
+ __decorate([
80
+ Fields.updatedAt()
81
+ ], KitAuthAccount.prototype, "updatedAt", void 0);
82
+ __decorate([
83
+ Fields.string()
84
+ ], KitAuthAccount.prototype, "userId", void 0);
85
+ __decorate([
86
+ Relations.toOne(() => KitAuthUser, 'userId')
87
+ ], KitAuthAccount.prototype, "user", void 0);
88
+ __decorate([
89
+ Fields.string()
90
+ ], KitAuthAccount.prototype, "provider", void 0);
91
+ __decorate([
92
+ Fields.string()
93
+ ], KitAuthAccount.prototype, "providerUserId", void 0);
94
+ __decorate([
95
+ Fields.string({ includeInApi: false, allowNull: true })
96
+ ], KitAuthAccount.prototype, "hashPassword", void 0);
97
+ __decorate([
98
+ Fields.string({ includeInApi: false, allowNull: true })
99
+ ], KitAuthAccount.prototype, "token", void 0);
100
+ __decorate([
101
+ Fields.date({ includeInApi: false, allowNull: true })
102
+ ], KitAuthAccount.prototype, "expiresAt", void 0);
103
+ __decorate([
104
+ Fields.date({ includeInApi: false, allowNull: true })
105
+ ], KitAuthAccount.prototype, "lastVerifiedAt", void 0);
106
+ KitAuthAccount = __decorate([
107
+ Entity('kit_auth_account', {
108
+ allowApiCrud: [KitAuthRole.Admin, KitRole.Admin],
109
+ dbName: 'auth.kit_auth_account',
110
+ id: { provider: true, userId: true },
111
+ })
112
+ ], KitAuthAccount);
113
+ export { KitAuthAccount };
114
+ let KitAuthUserSession = class KitAuthUserSession {
115
+ id;
116
+ expiresAt;
117
+ userId;
118
+ user;
119
+ };
120
+ __decorate([
121
+ Fields.cuid()
122
+ ], KitAuthUserSession.prototype, "id", void 0);
123
+ __decorate([
124
+ Fields.date()
125
+ ], KitAuthUserSession.prototype, "expiresAt", void 0);
126
+ __decorate([
127
+ Fields.string()
128
+ ], KitAuthUserSession.prototype, "userId", void 0);
129
+ __decorate([
130
+ Relations.toOne(() => KitAuthUser, 'userId')
131
+ ], KitAuthUserSession.prototype, "user", void 0);
132
+ KitAuthUserSession = __decorate([
133
+ Entity('kit_auth_session', {
134
+ allowApiCrud: [KitAuthRole.Admin, KitRole.Admin],
135
+ dbName: 'auth.kit_auth_session',
136
+ })
137
+ ], KitAuthUserSession);
138
+ export { KitAuthUserSession };
139
+ let AuthProvider = class AuthProvider extends KitBaseEnum {
140
+ static { AuthProvider_1 = this; }
141
+ static DEMO = new AuthProvider_1('DEMO', { caption: 'Demo' });
142
+ static PASSWORD = new AuthProvider_1('PASSWORD', { caption: 'Password' });
143
+ static OTP = new AuthProvider_1('OTP', { caption: 'TOTP' });
144
+ static OAUTH = new AuthProvider_1('OAUTH', { caption: 'OAUTH' });
145
+ constructor(id, o) {
146
+ super(id, {
147
+ ...o,
148
+ });
149
+ }
150
+ };
151
+ AuthProvider = AuthProvider_1 = __decorate([
152
+ ValueListFieldType()
153
+ ], AuthProvider);
154
+ export { AuthProvider };
@@ -0,0 +1,14 @@
1
+ import type { ClassType } from 'remult';
2
+ import { Log } from '@kitql/helpers';
3
+ import { KitAuthUser } from './Entities';
4
+ /**
5
+ * will merge the roles and remove duplicates
6
+ * will return a new array & a status if the array was changed
7
+ */
8
+ export declare const mergeRoles: (existing: string[], newOnes: string[] | undefined) => {
9
+ roles: string[];
10
+ changed: boolean;
11
+ };
12
+ export declare class RoleController {
13
+ static initRoleFromEnv: (log: Log, userEntity: ClassType<KitAuthUser>, envKey: string, role: string) => Promise<void>;
14
+ }