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.
- package/CHANGELOG.md +15 -0
- package/LICENSE +18 -0
- package/README.md +12 -0
- package/esm/KitBaseEnum.d.ts +35 -0
- package/esm/KitBaseEnum.js +32 -0
- package/esm/KitEntity.d.ts +2 -0
- package/esm/KitEntity.js +24 -0
- package/esm/KitFields.d.ts +10 -0
- package/esm/KitFields.js +196 -0
- package/esm/ROUTES.d.ts +88 -0
- package/esm/ROUTES.js +98 -0
- package/esm/SqlDatabase/LogToConsoleCustom.d.ts +1 -0
- package/esm/SqlDatabase/LogToConsoleCustom.js +102 -0
- package/esm/api/index.d.ts +42 -0
- package/esm/api/index.js +97 -0
- package/esm/auth/Adapter.d.ts +10 -0
- package/esm/auth/Adapter.js +54 -0
- package/esm/auth/AuthController.d.ts +59 -0
- package/esm/auth/AuthController.js +434 -0
- package/esm/auth/Entities.d.ts +39 -0
- package/esm/auth/Entities.js +154 -0
- package/esm/auth/RoleController.d.ts +14 -0
- package/esm/auth/RoleController.js +57 -0
- package/esm/auth/helper.d.ts +1 -0
- package/esm/auth/helper.js +7 -0
- package/esm/auth/index.d.ts +153 -0
- package/esm/auth/index.js +280 -0
- package/esm/auth/providers/github.d.ts +25 -0
- package/esm/auth/providers/github.js +51 -0
- package/esm/auth/providers/index.d.ts +3 -0
- package/esm/auth/providers/index.js +26 -0
- package/esm/auth/providers/strava.d.ts +25 -0
- package/esm/auth/providers/strava.js +51 -0
- package/esm/auth/static/assets/Page-BMFREPjF.d.ts +5 -0
- package/esm/auth/static/assets/Page-BMFREPjF.js +18 -0
- package/esm/auth/static/assets/Page-BMOLAIFx.d.ts +5 -0
- package/esm/auth/static/assets/Page-BMOLAIFx.js +1 -0
- package/esm/auth/static/assets/Page-BwHye0GW.d.ts +5 -0
- package/esm/auth/static/assets/Page-BwHye0GW.js +1 -0
- package/esm/auth/static/assets/Page-gV58jf2r.css +1 -0
- package/esm/auth/static/assets/index-CKmKKRRL.d.ts +53 -0
- package/esm/auth/static/assets/index-CKmKKRRL.js +2 -0
- package/esm/auth/static/assets/index-R27C_TlP.css +4 -0
- package/esm/auth/static/favicon.svg +79 -0
- package/esm/auth/static/index.html +14 -0
- package/esm/auth/types.d.ts +33 -0
- package/esm/auth/types.js +1 -0
- package/esm/bin/cmd.d.ts +1 -0
- package/esm/bin/cmd.js +418 -0
- package/esm/changeLog/index.d.ts +55 -0
- package/esm/changeLog/index.js +179 -0
- package/esm/cron/index.d.ts +60 -0
- package/esm/cron/index.js +102 -0
- package/esm/feedback/FeedbackController.d.ts +30 -0
- package/esm/feedback/FeedbackController.js +313 -0
- package/esm/feedback/index.d.ts +18 -0
- package/esm/feedback/index.js +14 -0
- package/esm/feedback/ui/DialogIssue.svelte +102 -0
- package/esm/feedback/ui/DialogIssue.svelte.d.ts +20 -0
- package/esm/feedback/ui/DialogIssues.svelte +91 -0
- package/esm/feedback/ui/DialogIssues.svelte.d.ts +20 -0
- package/esm/feedback/ui/DialogMilestones.svelte +38 -0
- package/esm/feedback/ui/DialogMilestones.svelte.d.ts +18 -0
- package/esm/feedback/ui/Feedback.svelte +12 -0
- package/esm/feedback/ui/Feedback.svelte.d.ts +16 -0
- package/esm/formats/dates.d.ts +18 -0
- package/esm/formats/dates.js +35 -0
- package/esm/formats/index.d.ts +4 -0
- package/esm/formats/index.js +3 -0
- package/esm/formats/numbers.d.ts +4 -0
- package/esm/formats/numbers.js +34 -0
- package/esm/formats/strings.d.ts +11 -0
- package/esm/formats/strings.js +109 -0
- package/esm/handle/index.d.ts +7 -0
- package/esm/handle/index.js +40 -0
- package/esm/helper.d.ts +50 -0
- package/esm/helper.js +118 -0
- package/esm/index.d.ts +103 -0
- package/esm/index.js +42 -0
- package/esm/kitCellsBuildor.d.ts +45 -0
- package/esm/kitCellsBuildor.js +105 -0
- package/esm/kitStoreItem.d.ts +28 -0
- package/esm/kitStoreItem.js +170 -0
- package/esm/kitStoreList.d.ts +33 -0
- package/esm/kitStoreList.js +98 -0
- package/esm/mail/index.d.ts +11 -0
- package/esm/mail/index.js +51 -0
- package/esm/theme.d.ts +4 -0
- package/esm/theme.js +4 -0
- package/esm/ui/Button.svelte +102 -0
- package/esm/ui/Button.svelte.d.ts +27 -0
- package/esm/ui/Clipboardable.svelte +19 -0
- package/esm/ui/Clipboardable.svelte.d.ts +25 -0
- package/esm/ui/Field.svelte +288 -0
- package/esm/ui/Field.svelte.d.ts +29 -0
- package/esm/ui/FieldGroup.svelte +91 -0
- package/esm/ui/FieldGroup.svelte.d.ts +30 -0
- package/esm/ui/Grid.svelte +246 -0
- package/esm/ui/Grid.svelte.d.ts +46 -0
- package/esm/ui/GridLoading.svelte +32 -0
- package/esm/ui/GridLoading.svelte.d.ts +20 -0
- package/esm/ui/GridPaginate.svelte +66 -0
- package/esm/ui/GridPaginate.svelte.d.ts +22 -0
- package/esm/ui/Icon.svelte +86 -0
- package/esm/ui/Icon.svelte.d.ts +46 -0
- package/esm/ui/LibIcon.d.ts +23 -0
- package/esm/ui/LibIcon.js +28 -0
- package/esm/ui/Loading.svelte +11 -0
- package/esm/ui/Loading.svelte.d.ts +20 -0
- package/esm/ui/Tooltip.svelte +42 -0
- package/esm/ui/Tooltip.svelte.d.ts +22 -0
- package/esm/ui/dialog/DialogForm.svelte +70 -0
- package/esm/ui/dialog/DialogForm.svelte.d.ts +19 -0
- package/esm/ui/dialog/DialogManagement.svelte +87 -0
- package/esm/ui/dialog/DialogManagement.svelte.d.ts +25 -0
- package/esm/ui/dialog/DialogPrimitive.svelte +89 -0
- package/esm/ui/dialog/DialogPrimitive.svelte.d.ts +28 -0
- package/esm/ui/dialog/FormEditAction.svelte +54 -0
- package/esm/ui/dialog/FormEditAction.svelte.d.ts +24 -0
- package/esm/ui/dialog/dialog.d.ts +51 -0
- package/esm/ui/dialog/dialog.js +98 -0
- package/esm/ui/index.d.ts +5 -0
- package/esm/ui/index.js +19 -0
- package/esm/ui/internals/FieldContainer.svelte +22 -0
- package/esm/ui/internals/FieldContainer.svelte.d.ts +30 -0
- package/esm/ui/internals/Input.svelte +98 -0
- package/esm/ui/internals/Input.svelte.d.ts +35 -0
- package/esm/ui/internals/Textarea.svelte +61 -0
- package/esm/ui/internals/Textarea.svelte.d.ts +30 -0
- package/esm/ui/internals/select/MultiSelectMelt.svelte +217 -0
- package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +30 -0
- package/esm/ui/internals/select/SelectMelt.svelte +238 -0
- package/esm/ui/internals/select/SelectMelt.svelte.d.ts +35 -0
- package/esm/ui/internals/select/SelectRadio.svelte +37 -0
- package/esm/ui/internals/select/SelectRadio.svelte.d.ts +25 -0
- package/esm/ui/link/Link.svelte +28 -0
- package/esm/ui/link/Link.svelte.d.ts +25 -0
- package/esm/ui/link/LinkPlus.svelte +44 -0
- package/esm/ui/link/LinkPlus.svelte.d.ts +21 -0
- package/esm/utils/tailwind.d.ts +2 -0
- package/esm/utils/tailwind.js +3 -0
- package/esm/utils/transition.d.ts +10 -0
- package/esm/utils/transition.js +33 -0
- package/esm/utils/types.d.ts +17 -0
- package/esm/utils/types.js +17 -0
- package/esm/virtual/Customer.d.ts +4 -0
- package/esm/virtual/Customer.js +24 -0
- package/esm/virtual/FilterEntity.d.ts +7 -0
- package/esm/virtual/FilterEntity.js +34 -0
- package/esm/virtual/StateDemoEnum.d.ts +9 -0
- package/esm/virtual/StateDemoEnum.js +42 -0
- package/esm/virtual/UIEntity.d.ts +16 -0
- package/esm/virtual/UIEntity.js +84 -0
- package/esm/vite/index.d.ts +8 -0
- package/esm/vite/index.js +47 -0
- 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
|
+
}
|