firstly 0.2.0 → 0.3.0
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 +23 -0
- package/esm/bin/cmd.js +3 -158
- package/esm/changeLog/index.d.ts +1 -6
- package/esm/feedback/FeedbackController.d.ts +6 -2
- package/esm/feedback/FeedbackController.js +145 -143
- package/esm/feedback/server/index.d.ts +2 -2
- package/esm/feedback/server/index.js +3 -3
- package/esm/feedback/types.d.ts +5 -0
- package/esm/feedback/ui/DialogIssue.svelte +5 -5
- package/esm/feedback/ui/DialogIssues.svelte +5 -5
- package/esm/feedback/ui/DialogMilestones.svelte +1 -1
- package/esm/internals/BaseEnum.d.ts +2 -1
- package/esm/internals/FF_Entity.js +1 -17
- package/esm/internals/FF_Fields.d.ts +4 -3
- package/esm/internals/FF_Fields.js +14 -55
- package/esm/internals/cellsBuildor.d.ts +2 -1
- package/esm/internals/cellsBuildor.js +5 -4
- package/esm/internals/index.d.ts +7 -12
- package/esm/internals/storeItem.d.ts +12 -20
- package/esm/internals/storeItem.js +20 -6
- package/esm/mail/server/index.d.ts +8 -2
- package/esm/mail/server/index.js +35 -7
- package/esm/server/index.d.ts +1 -1
- package/esm/svelte/FF_Cell.svelte +3 -5
- package/esm/svelte/FF_Cell.svelte.d.ts +4 -2
- package/esm/svelte/FF_Form.svelte +4 -5
- package/esm/svelte/FF_Grid.svelte +2 -2
- package/esm/svelte/FF_Layout.svelte +3 -3
- package/esm/svelte/FF_Repo.svelte.d.ts +9 -0
- package/esm/svelte/FF_Repo.svelte.js +39 -0
- package/esm/svelte/class/SP.svelte.js +14 -2
- package/esm/svelte/dialog/DialogManagement.svelte +2 -5
- package/esm/svelte/dialog/DialogPrimitive.svelte +1 -2
- package/esm/svelte/dialog/dialog.js +2 -2
- package/esm/svelte/ff_Config.svelte.js +2 -2
- package/esm/svelte/index.d.ts +2 -7
- package/esm/svelte/index.js +2 -7
- package/esm/ui/Button.svelte +34 -66
- package/esm/ui/Button.svelte.d.ts +9 -35
- package/esm/ui/Clipboardable.svelte +13 -17
- package/esm/ui/Clipboardable.svelte.d.ts +9 -33
- package/esm/ui/Field.svelte +48 -9
- package/esm/ui/FieldGroup.svelte.d.ts +1 -1
- package/esm/ui/Grid.svelte +13 -87
- package/esm/ui/Grid.svelte.d.ts +0 -1
- package/esm/ui/Grid2.svelte +26 -90
- package/esm/ui/Grid2.svelte.d.ts +1 -2
- package/esm/ui/GridPaginate.svelte +1 -1
- package/esm/ui/GridPaginate2.svelte +2 -2
- package/esm/ui/Icon.svelte +2 -18
- package/esm/ui/Icon.svelte.d.ts +0 -2
- package/esm/ui/LibIcon.js +2 -2
- package/esm/ui/Loading.svelte +1 -1
- package/esm/ui/dialog/DialogManagement.svelte +14 -5
- package/esm/ui/dialog/DialogPrimitive.svelte +3 -3
- package/esm/ui/dialog/FormEditAction.svelte +4 -4
- package/esm/ui/dialog/dialog.d.ts +5 -2
- package/esm/ui/dialog/dialog.js +2 -2
- package/esm/ui/index.d.ts +1 -0
- package/esm/ui/index.js +1 -0
- package/esm/ui/internals/FieldContainer.svelte +25 -14
- package/esm/ui/internals/FieldContainer.svelte.d.ts +9 -30
- package/esm/ui/internals/Input.svelte.d.ts +1 -1
- package/esm/ui/internals/Textarea.svelte +2 -5
- package/esm/ui/internals/select/MultiSelectMelt.svelte +10 -8
- package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +1 -1
- package/esm/ui/internals/select/Select2.svelte +88 -0
- package/esm/ui/internals/select/Select2.svelte.d.ts +12 -0
- package/esm/ui/internals/select/SelectMelt.svelte +33 -24
- package/esm/ui/internals/select/SelectMelt.svelte.d.ts +1 -1
- package/esm/ui/internals/select/SelectRadio.svelte +2 -2
- package/esm/ui/internals/select/SelectRadio.svelte.d.ts +1 -1
- package/esm/ui/link/Link.svelte +1 -1
- package/esm/ui/link/LinkPlus.svelte +9 -5
- package/esm/ui/link/LinkPlus.svelte.d.ts +5 -19
- package/esm/virtual/Customer.js +1 -2
- package/esm/virtual/UIEntity.js +9 -5
- package/package.json +11 -25
- package/esm/auth/AuthController.d.ts +0 -58
- package/esm/auth/AuthController.js +0 -114
- package/esm/auth/Entities.d.ts +0 -47
- package/esm/auth/Entities.js +0 -182
- package/esm/auth/README.md +0 -3
- package/esm/auth/index.d.ts +0 -5
- package/esm/auth/index.js +0 -5
- package/esm/auth/server/AuthController.server.d.ts +0 -58
- package/esm/auth/server/AuthController.server.js +0 -498
- package/esm/auth/server/handleAuth.d.ts +0 -4
- package/esm/auth/server/handleAuth.js +0 -142
- package/esm/auth/server/handleGuard.d.ts +0 -22
- package/esm/auth/server/handleGuard.js +0 -34
- package/esm/auth/server/helperDb.d.ts +0 -10
- package/esm/auth/server/helperDb.js +0 -56
- package/esm/auth/server/helperFirstly.d.ts +0 -1
- package/esm/auth/server/helperFirstly.js +0 -9
- package/esm/auth/server/helperOslo.d.ts +0 -7
- package/esm/auth/server/helperOslo.js +0 -24
- package/esm/auth/server/helperRemultServer.d.ts +0 -5
- package/esm/auth/server/helperRemultServer.js +0 -44
- package/esm/auth/server/helperRole.d.ts +0 -19
- package/esm/auth/server/helperRole.js +0 -57
- package/esm/auth/server/index.d.ts +0 -8
- package/esm/auth/server/index.js +0 -8
- package/esm/auth/server/module.d.ts +0 -300
- package/esm/auth/server/module.js +0 -230
- package/esm/auth/server/providers/github.d.ts +0 -33
- package/esm/auth/server/providers/github.js +0 -87
- package/esm/auth/server/providers/helperProvider.d.ts +0 -1
- package/esm/auth/server/providers/helperProvider.js +0 -25
- package/esm/auth/static/assets/Page-9Ytj29NS.d.ts +0 -2
- package/esm/auth/static/assets/Page-9Ytj29NS.js +0 -1
- package/esm/auth/static/assets/Page-BHW08QWz.css +0 -1
- package/esm/auth/static/assets/Page-C1pM-UDt.d.ts +0 -2
- package/esm/auth/static/assets/Page-C1pM-UDt.js +0 -20
- package/esm/auth/static/assets/Page-CPz6KCw_.d.ts +0 -2
- package/esm/auth/static/assets/Page-CPz6KCw_.js +0 -1
- package/esm/auth/static/assets/index-AoBb9Ds5.d.ts +0 -232
- package/esm/auth/static/assets/index-AoBb9Ds5.js +0 -2
- package/esm/auth/static/assets/index-DKWpA6v7.css +0 -4
- package/esm/auth/static/favicon.svg +0 -79
- package/esm/auth/static/index.html +0 -13
- package/esm/auth/types.d.ts +0 -73
- package/esm/auth/types.js +0 -1
- package/esm/svelte/FF_Display.svelte +0 -51
- package/esm/svelte/FF_Display.svelte.d.ts +0 -29
- package/esm/svelte/FF_Edit.svelte +0 -104
- package/esm/svelte/FF_Edit.svelte.d.ts +0 -32
- package/esm/svelte/FF_Error.svelte +0 -23
- package/esm/svelte/FF_Error.svelte.d.ts +0 -29
- package/esm/svelte/FF_Field.svelte +0 -62
- package/esm/svelte/FF_Field.svelte.d.ts +0 -29
- package/esm/svelte/FF_Hint.svelte +0 -21
- package/esm/svelte/FF_Hint.svelte.d.ts +0 -29
- package/esm/svelte/FF_Label.svelte +0 -23
- package/esm/svelte/FF_Label.svelte.d.ts +0 -29
|
@@ -1,498 +0,0 @@
|
|
|
1
|
-
import { decodeHex, encodeHexLowerCase } from '@oslojs/encoding';
|
|
2
|
-
import { createTOTPKeyURI, generateTOTP, verifyTOTPWithGracePeriod } from '@oslojs/otp';
|
|
3
|
-
import { generateState } from 'arctic';
|
|
4
|
-
import { EntityError, remult, repo } from 'remult';
|
|
5
|
-
import { nameify } from '../../formats/strings.js';
|
|
6
|
-
import { gray, green, magenta, red, yellow } from '@kitql/helpers';
|
|
7
|
-
import { FFAuthProvider } from '../Entities.js';
|
|
8
|
-
import { invalidateSession } from './helperDb.js';
|
|
9
|
-
import { ff_createSession } from './helperFirstly.js';
|
|
10
|
-
import { createDate, generateAndEncodeToken } from './helperOslo.js';
|
|
11
|
-
import { deleteSessionTokenCookie, setOAuthStateCookie, setRedirectCookie, } from './helperRemultServer.js';
|
|
12
|
-
import { mergeRoles } from './helperRole.js';
|
|
13
|
-
import { AUTH_OPTIONS, authModuleRaw, getSafeOptions } from './module.js';
|
|
14
|
-
const getSendMail = () => {
|
|
15
|
-
if (!remult.context.sendMail) {
|
|
16
|
-
authModuleRaw.log.error(`Missing ${green(`remult.context`)}.${red(`sendMail`)}`);
|
|
17
|
-
authModuleRaw.log.error('');
|
|
18
|
-
authModuleRaw.log.error(gray('Add this to your modules:'));
|
|
19
|
-
authModuleRaw.log.error('import { mail } from "../../mail/server"');
|
|
20
|
-
authModuleRaw.log.error('');
|
|
21
|
-
authModuleRaw.log.error('{');
|
|
22
|
-
authModuleRaw.log.error(` modules: [`);
|
|
23
|
-
authModuleRaw.log.error(` mail({`);
|
|
24
|
-
authModuleRaw.log.error(` // options`);
|
|
25
|
-
authModuleRaw.log.error(` })`);
|
|
26
|
-
authModuleRaw.log.error(` ]`);
|
|
27
|
-
authModuleRaw.log.error('}');
|
|
28
|
-
authModuleRaw.log.error('');
|
|
29
|
-
throw new EntityError({ message: 'Error: Contact your administrator.' });
|
|
30
|
-
}
|
|
31
|
-
return remult.context.sendMail;
|
|
32
|
-
};
|
|
33
|
-
export class AuthControllerServer {
|
|
34
|
-
/**
|
|
35
|
-
* Sign out the current user
|
|
36
|
-
*/
|
|
37
|
-
static async signOut() {
|
|
38
|
-
if (remult.user?.session.id) {
|
|
39
|
-
await invalidateSession(remult.user?.session.id);
|
|
40
|
-
}
|
|
41
|
-
deleteSessionTokenCookie();
|
|
42
|
-
return {
|
|
43
|
-
message: 'signed out',
|
|
44
|
-
user: undefined,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Sign in with a demo account
|
|
49
|
-
* _(The easiest way to demo & test your application)_
|
|
50
|
-
*/
|
|
51
|
-
static async signInDemo(name) {
|
|
52
|
-
const accounts = AUTH_OPTIONS.providers?.demo ?? [];
|
|
53
|
-
if (accounts.length === 0) {
|
|
54
|
-
throw new EntityError({ message: `Demo accounts are not enabled!` });
|
|
55
|
-
}
|
|
56
|
-
const accountConf = accounts.find((a) => a.name === name);
|
|
57
|
-
if (!accountConf) {
|
|
58
|
-
throw new EntityError({ message: `${name} not found as demo account!` });
|
|
59
|
-
}
|
|
60
|
-
const oSafe = getSafeOptions();
|
|
61
|
-
const user = await repo(oSafe.User).upsert({ where: { name } });
|
|
62
|
-
await repo(oSafe.Account).upsert({
|
|
63
|
-
where: {
|
|
64
|
-
provider: FFAuthProvider.DEMO.id,
|
|
65
|
-
providerUserId: name,
|
|
66
|
-
userId: user.id,
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
const r = mergeRoles(user.roles, accountConf.roles);
|
|
70
|
-
if (r.changed) {
|
|
71
|
-
user.roles = r.roles;
|
|
72
|
-
await repo(oSafe.User).save(user);
|
|
73
|
-
}
|
|
74
|
-
const session = await ff_createSession(user.id);
|
|
75
|
-
return {
|
|
76
|
-
message: `You're in with demo account!`,
|
|
77
|
-
user: oSafe.transformDbUserToClientUser(session, user),
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* This is for login / password authentication invite
|
|
82
|
-
*/
|
|
83
|
-
static async invite(emailParam) {
|
|
84
|
-
const email = emailParam?.toLowerCase();
|
|
85
|
-
const oSafe = getSafeOptions();
|
|
86
|
-
const existingAccount = await repo(oSafe.Account).findOne({
|
|
87
|
-
where: {
|
|
88
|
-
providerUserId: email,
|
|
89
|
-
provider: FFAuthProvider.PASSWORD.id,
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
if (existingAccount) {
|
|
93
|
-
// Already invited, it's all good.
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
const token = generateAndEncodeToken();
|
|
97
|
-
await repo(oSafe.Account).insert({
|
|
98
|
-
provider: FFAuthProvider.PASSWORD.id,
|
|
99
|
-
providerUserId: email,
|
|
100
|
-
email,
|
|
101
|
-
token,
|
|
102
|
-
expiresAt: createDate(AUTH_OPTIONS.providers?.password?.mail?.verify?.expiresIn ?? 5 * 60),
|
|
103
|
-
lastVerifiedAt: undefined,
|
|
104
|
-
});
|
|
105
|
-
const url = `${remult.context.request.url.origin}${oSafe.firstlyData.props.ui?.paths.reset_password}?token=${token}`;
|
|
106
|
-
if (AUTH_OPTIONS?.invitationSend) {
|
|
107
|
-
await AUTH_OPTIONS?.invitationSend({ email, url });
|
|
108
|
-
authModuleRaw.log.success(`${green('[custom]')}${magenta('[invitationSend]')} (${yellow(url)})`);
|
|
109
|
-
return {
|
|
110
|
-
message: 'Mail sent !',
|
|
111
|
-
user: remult.user,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
const sendMail = getSendMail();
|
|
116
|
-
await sendMail('invitationSend', {
|
|
117
|
-
to: email,
|
|
118
|
-
subject: 'Invitation',
|
|
119
|
-
title: 'Invitation 👋',
|
|
120
|
-
sections: [
|
|
121
|
-
{
|
|
122
|
-
html: 'Today is your lucky day !',
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
html: 'You were invited to join the team',
|
|
126
|
-
cta: {
|
|
127
|
-
html: 'JOIN',
|
|
128
|
-
link: url,
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
});
|
|
133
|
-
authModuleRaw.log.success(`${magenta('[invitationSend]')} (${yellow(url)})`);
|
|
134
|
-
return {
|
|
135
|
-
message: 'Demo Mail sent !',
|
|
136
|
-
user: remult.user,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
message: 'ok',
|
|
142
|
-
user: remult.user,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* This is for login / password authentication SignUp
|
|
147
|
-
* _(The first param `email` can be "anything")_
|
|
148
|
-
*/
|
|
149
|
-
static async signUpPassword(emailParam, password) {
|
|
150
|
-
const oSafe = getSafeOptions();
|
|
151
|
-
if (!oSafe.signUp) {
|
|
152
|
-
throw new EntityError({ message: oSafe.strings.cannotSignUp });
|
|
153
|
-
}
|
|
154
|
-
if (!oSafe.password.enabled) {
|
|
155
|
-
throw new EntityError({ message: 'Password is not enabled!' });
|
|
156
|
-
}
|
|
157
|
-
const email = emailParam?.toLowerCase();
|
|
158
|
-
oSafe.password.validateInput({ identifier: email, password });
|
|
159
|
-
const existingAccount = await repo(oSafe.Account).findOne({
|
|
160
|
-
where: {
|
|
161
|
-
providerUserId: email,
|
|
162
|
-
provider: FFAuthProvider.PASSWORD.id,
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
if (existingAccount) {
|
|
166
|
-
throw new EntityError({ message: "You can't signup twice !" });
|
|
167
|
-
}
|
|
168
|
-
const token = generateAndEncodeToken();
|
|
169
|
-
// REMULT: Do not put it in a transaction, as it will be called from a backendmethod that is already in a transaction! And nested transactions not allowed.
|
|
170
|
-
// await remult.dataProvider.transaction(async () => {
|
|
171
|
-
const user = await repo(oSafe.User).insert({
|
|
172
|
-
name: nameify(email),
|
|
173
|
-
});
|
|
174
|
-
await repo(oSafe.Account).insert({
|
|
175
|
-
provider: FFAuthProvider.PASSWORD.id,
|
|
176
|
-
providerUserId: email,
|
|
177
|
-
email,
|
|
178
|
-
userId: user.id,
|
|
179
|
-
hashPassword: await oSafe.password.hash(password),
|
|
180
|
-
token: oSafe.verifiedMethod === 'auto' ? undefined : token,
|
|
181
|
-
expiresAt: oSafe.verifiedMethod === 'auto'
|
|
182
|
-
? undefined
|
|
183
|
-
: createDate(AUTH_OPTIONS.providers?.password?.mail?.verify?.expiresIn ?? 5 * 60),
|
|
184
|
-
lastVerifiedAt: oSafe.verifiedMethod === 'auto' ? new Date() : undefined,
|
|
185
|
-
});
|
|
186
|
-
// })
|
|
187
|
-
if (oSafe.verifiedMethod === 'auto') {
|
|
188
|
-
if (user) {
|
|
189
|
-
const session = await ff_createSession(user.id);
|
|
190
|
-
return {
|
|
191
|
-
message: 'ok',
|
|
192
|
-
user: oSafe.transformDbUserToClientUser(session, user),
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
else if (oSafe.verifiedMethod === 'email') {
|
|
197
|
-
const url = `${remult.context.request.url.origin}${oSafe.firstlyData.props.ui?.paths.verify_email}?token=${token}`;
|
|
198
|
-
if (AUTH_OPTIONS.providers?.password?.mail?.verify?.send) {
|
|
199
|
-
await AUTH_OPTIONS.providers?.password.mail.verify.send({ email, url });
|
|
200
|
-
authModuleRaw.log.success(`${green('[custom]')}${magenta('[verifyMailSend]')} (${yellow(url)})`);
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
const sendMail = getSendMail();
|
|
204
|
-
await sendMail('verifyMailSend', {
|
|
205
|
-
to: email,
|
|
206
|
-
subject: 'Wecome',
|
|
207
|
-
title: 'Wecome 👋',
|
|
208
|
-
sections: [
|
|
209
|
-
{
|
|
210
|
-
html: 'You can validate your account',
|
|
211
|
-
cta: {
|
|
212
|
-
html: 'HERE',
|
|
213
|
-
link: url,
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
});
|
|
218
|
-
authModuleRaw.log.success(`${magenta('[verifyMailSend]')} (${yellow(url)})`);
|
|
219
|
-
}
|
|
220
|
-
return {
|
|
221
|
-
message: 'We sent you a mail to verify your account.',
|
|
222
|
-
user: undefined,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
message: 'Someone needs to validate your account.',
|
|
227
|
-
user: undefined,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* This is for login / password authentication SignIn
|
|
232
|
-
* _(The first param `email` can be "anything")_
|
|
233
|
-
*/
|
|
234
|
-
static async signInPassword(emailParam, password) {
|
|
235
|
-
const email = emailParam?.toLowerCase();
|
|
236
|
-
const oSafe = getSafeOptions();
|
|
237
|
-
oSafe.password.validateInput({ identifier: email, password });
|
|
238
|
-
if (!oSafe.password.enabled) {
|
|
239
|
-
throw new EntityError({ message: 'Password is not enabled!' });
|
|
240
|
-
}
|
|
241
|
-
const existingAccount = await repo(oSafe.Account).findOne({
|
|
242
|
-
where: {
|
|
243
|
-
providerUserId: email,
|
|
244
|
-
provider: FFAuthProvider.PASSWORD.id,
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
if (existingAccount) {
|
|
248
|
-
const validPassword = oSafe.password.verify(password ?? '', existingAccount?.hashPassword ?? '');
|
|
249
|
-
if (validPassword) {
|
|
250
|
-
const session = await ff_createSession(existingAccount.userId);
|
|
251
|
-
const user = await repo(oSafe.User).findId(existingAccount.userId);
|
|
252
|
-
if (!user) {
|
|
253
|
-
authModuleRaw.log.error('User not found for this arround:', existingAccount);
|
|
254
|
-
throw new EntityError({ message: 'User not found, please contact support.' });
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
message: 'Signing in progress...',
|
|
258
|
-
user: oSafe.transformDbUserToClientUser(session, user),
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
authModuleRaw.log.error({ email, passwordLengthInfo: password.length });
|
|
262
|
-
throw new EntityError({ message: 'Incorrect username or password' });
|
|
263
|
-
}
|
|
264
|
-
authModuleRaw.log.error({ email, passwordLengthInfo: password.length });
|
|
265
|
-
throw new EntityError({ message: 'Incorrect username or password.' });
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Forgot your password ? Send a mail to reset it.
|
|
269
|
-
*/
|
|
270
|
-
static async forgotPassword(emailParam) {
|
|
271
|
-
const email = emailParam?.toLowerCase();
|
|
272
|
-
const oSafe = getSafeOptions();
|
|
273
|
-
if (!oSafe.password.enabled) {
|
|
274
|
-
throw new EntityError({ message: 'Password is not enabled!' });
|
|
275
|
-
}
|
|
276
|
-
const existingAccount = await repo(oSafe.Account).findOne({
|
|
277
|
-
where: {
|
|
278
|
-
providerUserId: email,
|
|
279
|
-
provider: FFAuthProvider.PASSWORD.id,
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
if (existingAccount) {
|
|
283
|
-
const token = generateAndEncodeToken();
|
|
284
|
-
existingAccount.token = token;
|
|
285
|
-
existingAccount.expiresAt = createDate(AUTH_OPTIONS.providers?.password?.mail?.reset?.expiresIn ?? 5 * 60);
|
|
286
|
-
await repo(oSafe.Account).save(existingAccount);
|
|
287
|
-
const url = `${remult.context.request.url.origin}${oSafe.firstlyData.props.ui?.paths.reset_password}?token=${token}`;
|
|
288
|
-
if (AUTH_OPTIONS.providers?.password?.mail?.reset?.send) {
|
|
289
|
-
await AUTH_OPTIONS.providers?.password.mail.reset.send({ email, url });
|
|
290
|
-
authModuleRaw.log.success(`${green('[custom]')}${magenta('[resetPasswordSend]')} (${yellow(url)})`);
|
|
291
|
-
return {
|
|
292
|
-
message: oSafe.strings.resetPasswordSend,
|
|
293
|
-
user: remult.user,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
const sendMail = getSendMail();
|
|
298
|
-
await sendMail('resetPasswordSend', {
|
|
299
|
-
to: email,
|
|
300
|
-
subject: 'Reset your password',
|
|
301
|
-
sections: [
|
|
302
|
-
{
|
|
303
|
-
html: 'Did you forgot something ?',
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
html: 'No worries, you can reset your password',
|
|
307
|
-
cta: {
|
|
308
|
-
html: 'HERE',
|
|
309
|
-
link: url,
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
});
|
|
314
|
-
authModuleRaw.log.success(`${magenta('[resetPasswordSend]')} (${yellow(url)})`);
|
|
315
|
-
return {
|
|
316
|
-
message: `${oSafe.strings.resetPasswordSend} (DEMO)`,
|
|
317
|
-
user: remult.user,
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return {
|
|
322
|
-
message: oSafe.strings.resetPasswordUnknownUser,
|
|
323
|
-
user: remult.user,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Reset your password with a token
|
|
328
|
-
*/
|
|
329
|
-
static async resetPassword(token, password) {
|
|
330
|
-
const oSafe = getSafeOptions();
|
|
331
|
-
if (!oSafe.password.enabled) {
|
|
332
|
-
throw new EntityError({ message: 'Password is not enabled!' });
|
|
333
|
-
}
|
|
334
|
-
oSafe.password.validateInput({ identifier: 'resetPassword', password });
|
|
335
|
-
const account = await remult
|
|
336
|
-
.repo(oSafe.Account)
|
|
337
|
-
.findFirst({ token, provider: FFAuthProvider.PASSWORD.id });
|
|
338
|
-
if (!account) {
|
|
339
|
-
throw new EntityError({ message: 'Invalid token' });
|
|
340
|
-
}
|
|
341
|
-
if (account.expiresAt && account.expiresAt < new Date()) {
|
|
342
|
-
throw new EntityError({ message: 'token expired' });
|
|
343
|
-
}
|
|
344
|
-
if (account.userId === undefined) {
|
|
345
|
-
const user = await repo(oSafe.User).insert({ name: nameify(account.providerUserId) });
|
|
346
|
-
account.userId = user.id;
|
|
347
|
-
}
|
|
348
|
-
await invalidateSession(account.userId);
|
|
349
|
-
// update elements
|
|
350
|
-
account.hashPassword = await oSafe.password.hash(password);
|
|
351
|
-
account.token = undefined;
|
|
352
|
-
account.expiresAt = undefined;
|
|
353
|
-
account.lastVerifiedAt = new Date();
|
|
354
|
-
await repo(oSafe.Account).save(account);
|
|
355
|
-
await ff_createSession(account.userId);
|
|
356
|
-
return {
|
|
357
|
-
message: 'reseted',
|
|
358
|
-
user: remult.user,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
/** OTP */
|
|
362
|
-
static async signInOTP(emailParam) {
|
|
363
|
-
const email = emailParam?.toLowerCase();
|
|
364
|
-
const oSafe = getSafeOptions();
|
|
365
|
-
if (!oSafe.otp.enabled) {
|
|
366
|
-
throw new EntityError({ message: `OPT is not enabled!` });
|
|
367
|
-
}
|
|
368
|
-
if (AUTH_OPTIONS.providers?.otp?.send) {
|
|
369
|
-
const key = crypto.getRandomValues(new Uint8Array(20));
|
|
370
|
-
const intervalInSeconds = AUTH_OPTIONS.providers?.otp?.expiresIn ?? 30;
|
|
371
|
-
const digits = AUTH_OPTIONS.providers?.otp.digits ?? 6;
|
|
372
|
-
const otp = generateTOTP(key, intervalInSeconds, digits);
|
|
373
|
-
const keyEncoded = encodeHexLowerCase(key);
|
|
374
|
-
const issuer = AUTH_OPTIONS.providers.otp.issuer ?? 'firstly';
|
|
375
|
-
const uri = createTOTPKeyURI(issuer, email, key, intervalInSeconds, digits);
|
|
376
|
-
const oSafe = getSafeOptions();
|
|
377
|
-
const account = await repo(oSafe.Account).upsert({
|
|
378
|
-
where: {
|
|
379
|
-
provider: FFAuthProvider.OTP.id,
|
|
380
|
-
providerUserId: email,
|
|
381
|
-
},
|
|
382
|
-
});
|
|
383
|
-
if (account.userId === '' || account.userId === undefined) {
|
|
384
|
-
const user = await repo(oSafe.User).insert({ name: nameify(email) });
|
|
385
|
-
account.userId = user.id;
|
|
386
|
-
}
|
|
387
|
-
account.token = otp;
|
|
388
|
-
account.hashPassword = keyEncoded;
|
|
389
|
-
await repo(oSafe.Account).save(account);
|
|
390
|
-
await AUTH_OPTIONS.providers.otp?.send({ name: email, otp, uri });
|
|
391
|
-
authModuleRaw.log.success(`name: ${yellow(email)}, otp: ${yellow(otp)}, uri: ${yellow(uri)}`);
|
|
392
|
-
return {
|
|
393
|
-
message: 'Mail sent !',
|
|
394
|
-
user: remult.user,
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
else {
|
|
398
|
-
authModuleRaw.log.error(`You need to provide a otp.send hook in the auth options!`);
|
|
399
|
-
}
|
|
400
|
-
return {
|
|
401
|
-
message: 'Hum, something went wrong !',
|
|
402
|
-
user: remult.user,
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Verify the OTP code
|
|
407
|
-
*/
|
|
408
|
-
static async verifyOtp(emailParam, otp) {
|
|
409
|
-
const email = emailParam?.toLowerCase();
|
|
410
|
-
const oSafe = getSafeOptions();
|
|
411
|
-
if (!oSafe.otp.enabled) {
|
|
412
|
-
throw new EntityError({ message: `OPT is not enabled!` });
|
|
413
|
-
}
|
|
414
|
-
const accounts = await repo(oSafe.Account).find({
|
|
415
|
-
where: { token: String(otp), provider: FFAuthProvider.OTP.id },
|
|
416
|
-
});
|
|
417
|
-
if (accounts.length === 0) {
|
|
418
|
-
throw new EntityError({ message: 'Invalid otp' });
|
|
419
|
-
}
|
|
420
|
-
const account = accounts[0];
|
|
421
|
-
if (account?.providerUserId !== email) {
|
|
422
|
-
throw new EntityError({ message: 'Invalid otp.' });
|
|
423
|
-
}
|
|
424
|
-
const intervalInSeconds = oSafe.providers?.otp?.expiresIn ?? 30;
|
|
425
|
-
const digits = oSafe.providers?.otp?.digits ?? 6;
|
|
426
|
-
const keyDecoded = decodeHex(account.hashPassword ?? '');
|
|
427
|
-
const validOTP = verifyTOTPWithGracePeriod(keyDecoded, intervalInSeconds, digits, otp, 30);
|
|
428
|
-
if (!validOTP) {
|
|
429
|
-
throw new EntityError({ message: 'Invalid otp!' });
|
|
430
|
-
}
|
|
431
|
-
await invalidateSession(account.userId);
|
|
432
|
-
// update elements
|
|
433
|
-
account.hashPassword = undefined;
|
|
434
|
-
account.token = undefined;
|
|
435
|
-
account.expiresAt = undefined;
|
|
436
|
-
await repo(oSafe.Account).save(account);
|
|
437
|
-
const session = await ff_createSession(account.userId);
|
|
438
|
-
const user = await repo(oSafe.User).findId(account.userId);
|
|
439
|
-
if (!user) {
|
|
440
|
-
authModuleRaw.log.error('User not found for this arround:', account);
|
|
441
|
-
throw new EntityError({ message: 'User not found, please contact support.' });
|
|
442
|
-
}
|
|
443
|
-
return {
|
|
444
|
-
message: 'verified',
|
|
445
|
-
user: oSafe.transformDbUserToClientUser(session, user),
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
/** OAUTH */
|
|
449
|
-
/**
|
|
450
|
-
* The the url to redirect to for the OAuth provider
|
|
451
|
-
* @param provider Has to mach one of `AUTH_OPTIONS.providers.oAuths` your configured
|
|
452
|
-
*
|
|
453
|
-
* To be used like this for example:
|
|
454
|
-
* ```
|
|
455
|
-
* const url = await Auth.signInOAuthGetUrl('github')
|
|
456
|
-
* window.location.href = url
|
|
457
|
-
* ```
|
|
458
|
-
*
|
|
459
|
-
* _(popup example should work too, and a nice example/componant would be appreciated)_
|
|
460
|
-
*/
|
|
461
|
-
static async signInOAuthGetUrl(o) {
|
|
462
|
-
const selectedOAuth = AUTH_OPTIONS.providers?.oAuths?.find((c) => c.name === o.provider);
|
|
463
|
-
if (selectedOAuth) {
|
|
464
|
-
const state = generateState();
|
|
465
|
-
try {
|
|
466
|
-
const arcticProvider = selectedOAuth.getArcticProvider();
|
|
467
|
-
const args = [state];
|
|
468
|
-
if (o.options) {
|
|
469
|
-
args.push(o.options);
|
|
470
|
-
}
|
|
471
|
-
else {
|
|
472
|
-
if (selectedOAuth.authorizationURLOptions) {
|
|
473
|
-
args.push(selectedOAuth.authorizationURLOptions());
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
const url = await arcticProvider.createAuthorizationURL(...args);
|
|
477
|
-
if (!url) {
|
|
478
|
-
throw new EntityError({ message: 'No url returned' });
|
|
479
|
-
}
|
|
480
|
-
setOAuthStateCookie(selectedOAuth.name, state);
|
|
481
|
-
if (o.redirect) {
|
|
482
|
-
setRedirectCookie(o.redirect);
|
|
483
|
-
}
|
|
484
|
-
return url.toString();
|
|
485
|
-
}
|
|
486
|
-
catch (error) {
|
|
487
|
-
// display error for the server only
|
|
488
|
-
authModuleRaw.log.error(error);
|
|
489
|
-
throw new EntityError({
|
|
490
|
-
message: `${selectedOAuth.name} not well configured! Check server logs for more details.`,
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
throw new EntityError({
|
|
495
|
-
message: `${o.provider} is not configured! (Module: auth, section: providers.oAuths: [${o.provider}] missing)`,
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { redirect as redirectKit } from '@sveltejs/kit';
|
|
2
|
-
import { repo } from 'remult';
|
|
3
|
-
import { read } from '@kitql/internals';
|
|
4
|
-
import { FFAuthProvider } from '../Entities';
|
|
5
|
-
import { ff_createSession } from './helperFirstly';
|
|
6
|
-
import { getSafeOptions } from './module';
|
|
7
|
-
export const handleAuth = ({ redirect }) => async ({ event, resolve }) => {
|
|
8
|
-
const oSafe = getSafeOptions();
|
|
9
|
-
if (event.url.pathname === oSafe.firstlyData.props.ui?.paths?.verify_email) {
|
|
10
|
-
const token = event.url.searchParams.get('token') ?? '';
|
|
11
|
-
if (!oSafe.password.enabled) {
|
|
12
|
-
throw Error('Password is not enabled!');
|
|
13
|
-
}
|
|
14
|
-
const account = await repo(oSafe.Account).findFirst({
|
|
15
|
-
token,
|
|
16
|
-
provider: FFAuthProvider.PASSWORD.id,
|
|
17
|
-
});
|
|
18
|
-
if (!account) {
|
|
19
|
-
throw new Error('Invalid token');
|
|
20
|
-
}
|
|
21
|
-
if (account.expiresAt && account.expiresAt < new Date()) {
|
|
22
|
-
throw new Error('token expired');
|
|
23
|
-
}
|
|
24
|
-
// update elements
|
|
25
|
-
account.token = undefined;
|
|
26
|
-
account.expiresAt = undefined;
|
|
27
|
-
account.lastVerifiedAt = new Date();
|
|
28
|
-
await repo(oSafe.Account).save(account);
|
|
29
|
-
await ff_createSession(account.userId);
|
|
30
|
-
redirect(302, oSafe.redirectUrl);
|
|
31
|
-
}
|
|
32
|
-
if (oSafe.firstlyData.props.ui?.paths?.base &&
|
|
33
|
-
event.url.pathname.startsWith(oSafe.firstlyData.props.ui.paths.base)) {
|
|
34
|
-
let content = read(`${oSafe.uiStaticPath}index.html`) ?? '';
|
|
35
|
-
content = content?.replaceAll('<!--PLACE_HERE_HEAD-->', oSafe.ui?.customHtmlHead ?? '');
|
|
36
|
-
return new Response(content + `<script>const firstlyData = ${JSON.stringify(oSafe.firstlyData)}</script>`, {
|
|
37
|
-
headers: { 'content-type': 'text/html' },
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
if (event.url.pathname.startsWith('/api/static')) {
|
|
41
|
-
const content = read(`${oSafe.uiStaticPath}${event.url.pathname.replaceAll('/api/static/', '')}`);
|
|
42
|
-
if (content) {
|
|
43
|
-
const seg = event.url.pathname.split('.');
|
|
44
|
-
const map = {
|
|
45
|
-
js: 'text/javascript',
|
|
46
|
-
css: 'text/css',
|
|
47
|
-
svg: 'image/svg+xml',
|
|
48
|
-
};
|
|
49
|
-
return new Response(content, {
|
|
50
|
-
headers: { 'content-type': map[seg[seg.length - 1]] ?? 'text/plain' },
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (event.url.pathname === '/api/auth_callback') {
|
|
55
|
-
const code = event.url.searchParams.get('code');
|
|
56
|
-
const state = event.url.searchParams.get('state');
|
|
57
|
-
const keys = oSafe.providers?.oAuths?.map((c) => c.name) ?? [];
|
|
58
|
-
let storedState = null;
|
|
59
|
-
let keyState = null;
|
|
60
|
-
for (const key of keys) {
|
|
61
|
-
storedState = event.cookies.get(`${key}_oauth_state`) ?? null;
|
|
62
|
-
if (storedState) {
|
|
63
|
-
keyState = key;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const redirectUrlCookie = event.cookies.get(`remult_redirect`);
|
|
68
|
-
if (redirectUrlCookie) {
|
|
69
|
-
event.cookies.delete(`remult_redirect`, { path: '/' });
|
|
70
|
-
}
|
|
71
|
-
const redirectUrl = redirectUrlCookie ?? oSafe.redirectUrl;
|
|
72
|
-
if (!code || !state || !storedState || state !== storedState || !keyState) {
|
|
73
|
-
redirect(302, redirectUrl);
|
|
74
|
-
// We have to do this because "lib redirect" is not working, so I pass the handle... :(
|
|
75
|
-
throw 'we cannot arrive here!';
|
|
76
|
-
}
|
|
77
|
-
const selectedOAuth = oSafe.providers?.oAuths?.find((c) => c.name === keyState);
|
|
78
|
-
if (selectedOAuth && code) {
|
|
79
|
-
const tokens = (await selectedOAuth
|
|
80
|
-
.getArcticProvider()
|
|
81
|
-
.validateAuthorizationCode(code));
|
|
82
|
-
let info;
|
|
83
|
-
try {
|
|
84
|
-
info = await selectedOAuth.getUserInfo(tokens);
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
console.error(error);
|
|
88
|
-
redirect(302, redirectUrl);
|
|
89
|
-
throw 'we cannot arrive here!';
|
|
90
|
-
}
|
|
91
|
-
if (!info.providerUserId) {
|
|
92
|
-
redirect(302, redirectUrl);
|
|
93
|
-
}
|
|
94
|
-
let account = await repo(oSafe.Account).findFirst({
|
|
95
|
-
provider: keyState,
|
|
96
|
-
providerUserId: info.providerUserId,
|
|
97
|
-
});
|
|
98
|
-
if (!account) {
|
|
99
|
-
if (!oSafe.signUp) {
|
|
100
|
-
console.error("You can't signup by yourself! Contact the administrator.");
|
|
101
|
-
// throw Error("You can't signup by yourself! Contact the administrator.")
|
|
102
|
-
redirect(302, redirectUrl);
|
|
103
|
-
}
|
|
104
|
-
// for each info.name, we check if it exists take the first option
|
|
105
|
-
// and add the providerUserId to the name if no option available
|
|
106
|
-
let nameToUse = '';
|
|
107
|
-
for (let i = 0; i < info.nameOptions.length; i++) {
|
|
108
|
-
const existingUser = await repo(oSafe.User).findOne({
|
|
109
|
-
where: { name: info.nameOptions[i] },
|
|
110
|
-
});
|
|
111
|
-
if (existingUser) {
|
|
112
|
-
// Don't do anything
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
nameToUse = info.nameOptions[i];
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (nameToUse === '') {
|
|
120
|
-
nameToUse = `${info.nameOptions[0]}-${info.providerUserId}`;
|
|
121
|
-
}
|
|
122
|
-
const user = repo(oSafe.User).create();
|
|
123
|
-
user.name = nameToUse;
|
|
124
|
-
await repo(oSafe.User).save(user);
|
|
125
|
-
account = repo(oSafe.Account).create();
|
|
126
|
-
account.provider = keyState;
|
|
127
|
-
account.providerUserId = info.providerUserId;
|
|
128
|
-
account.userId = user.id;
|
|
129
|
-
}
|
|
130
|
-
account.lastVerifiedAt = new Date();
|
|
131
|
-
account.token = tokens.accessToken();
|
|
132
|
-
account.metadata = { ...info, tokens_data: tokens.data };
|
|
133
|
-
account.email = info.emailOptions.length > 0 ? info.emailOptions[0] : undefined;
|
|
134
|
-
await repo(oSafe.Account).save(account);
|
|
135
|
-
await ff_createSession(account.userId);
|
|
136
|
-
event.cookies.delete(`${keyState}_oauth_state`, { path: '/' });
|
|
137
|
-
event.cookies.delete(`code_verifier`, { path: '/' });
|
|
138
|
-
}
|
|
139
|
-
redirect(302, redirectUrl);
|
|
140
|
-
}
|
|
141
|
-
return resolve(event);
|
|
142
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type Handle } from '@sveltejs/kit';
|
|
2
|
-
export type RouteGuardConfig = {
|
|
3
|
-
/**
|
|
4
|
-
* Routes that require authentication (e.g. `{ path: '/app*' }`)
|
|
5
|
-
*/
|
|
6
|
-
guard: {
|
|
7
|
-
path: string;
|
|
8
|
-
}[];
|
|
9
|
-
/**
|
|
10
|
-
* Where to redirect when authentication is required but user is not logged in
|
|
11
|
-
*/
|
|
12
|
-
login: string;
|
|
13
|
-
/**
|
|
14
|
-
* We need this import
|
|
15
|
-
*
|
|
16
|
-
* `import { redirect } from '@sveltejs/kit'` */
|
|
17
|
-
redirect: (status: number, url: string) => void;
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Creates a handle function with the provided route guard configuration
|
|
21
|
-
*/
|
|
22
|
-
export declare function handleGuard(config: RouteGuardConfig): Handle;
|