firstly 0.0.1 → 0.0.2
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 +8 -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 +279 -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 +408 -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,57 @@
|
|
|
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 { BackendMethod, repo } from 'remult';
|
|
8
|
+
import { cyan, green, Log, yellow } from '@kitql/helpers';
|
|
9
|
+
import { KitAuthUser } from './Entities';
|
|
10
|
+
/**
|
|
11
|
+
* will merge the roles and remove duplicates
|
|
12
|
+
* will return a new array & a status if the array was changed
|
|
13
|
+
*/
|
|
14
|
+
export const mergeRoles = (existing, newOnes) => {
|
|
15
|
+
const result = new Set(existing);
|
|
16
|
+
let changed = false;
|
|
17
|
+
for (const role of newOnes ?? []) {
|
|
18
|
+
if (!result.has(role)) {
|
|
19
|
+
result.add(role);
|
|
20
|
+
changed = true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { roles: Array.from(result), changed };
|
|
24
|
+
};
|
|
25
|
+
export class RoleController {
|
|
26
|
+
// @ts-ignore (for pnpm check)
|
|
27
|
+
static initRoleFromEnv = async (log, userEntity, envKey, role) => {
|
|
28
|
+
// @ts-ignore (for pnpm check)
|
|
29
|
+
const { env } = await import('$env/dynamic/private');
|
|
30
|
+
const names = env[envKey] === undefined ? [] : (env[envKey] ?? '').split(',');
|
|
31
|
+
for (let i = 0; i < names.length; i++) {
|
|
32
|
+
const name = names[i].trim();
|
|
33
|
+
if (name !== '') {
|
|
34
|
+
let user = await repo(userEntity).findFirst({ name });
|
|
35
|
+
if (!user) {
|
|
36
|
+
user = repo(userEntity).create({ name, roles: [role] });
|
|
37
|
+
await repo(userEntity).save(user);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
if (!user.roles.includes(role)) {
|
|
41
|
+
user.roles.push(role);
|
|
42
|
+
await repo(userEntity).save(user);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (names.length > 0) {
|
|
48
|
+
log.info(`${cyan(role)}: ${names.map((c) => green(c.trim())).join(', ')} added via ${yellow(envKey)}.`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
log.info(`${cyan(role)}: No users added via ${yellow(envKey)}.`);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
__decorate([
|
|
56
|
+
BackendMethod({ allowed: false })
|
|
57
|
+
], RoleController, "initRoleFromEnv", void 0);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createSession(userId: string): Promise<void>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { remult } from 'remult';
|
|
2
|
+
import { lucia } from '.';
|
|
3
|
+
export async function createSession(userId) {
|
|
4
|
+
const session = await lucia.createSession(userId, {});
|
|
5
|
+
const sessionCookie = lucia.createSessionCookie(session.id);
|
|
6
|
+
remult.context.setCookie(sessionCookie.name, sessionCookie.value, { path: '/' });
|
|
7
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { OAuth2Provider as ArcticOAuth2Provider, OAuth2ProviderWithPKCE as ArcticOAuth2ProviderWithPKCE } from 'arctic';
|
|
2
|
+
import { Lucia, type SessionCookieOptions } from 'lucia';
|
|
3
|
+
import type { ClassType, UserInfo } from 'remult';
|
|
4
|
+
import { Log } from '@kitql/helpers';
|
|
5
|
+
import type { Module } from '../api';
|
|
6
|
+
import type { ResolvedType } from '../utils/types';
|
|
7
|
+
import { KitAuthAccount, KitAuthUser, KitAuthUserSession } from './Entities';
|
|
8
|
+
import type { firstlyData } from './types';
|
|
9
|
+
export type { firstlyData };
|
|
10
|
+
export { KitAuthUser, KitAuthAccount, AuthProvider, KitAuthUserSession } from './Entities';
|
|
11
|
+
export { AuthController } from './AuthController';
|
|
12
|
+
export type AuthorizationURLOptions = Record<string, {
|
|
13
|
+
scopes?: string[];
|
|
14
|
+
}>;
|
|
15
|
+
export type DynamicAuthorizationURLOptions<T extends KitOAuth2Provider[] = KitOAuth2Provider[]> = T extends Array<infer O> ? O extends KitOAuth2Provider ? {
|
|
16
|
+
[P in O['name']]: ReturnType<O['authorizationURLOptions']>;
|
|
17
|
+
} : never : never;
|
|
18
|
+
export declare const logAuth: Log;
|
|
19
|
+
export { KitAuthRole } from './Entities';
|
|
20
|
+
type OAuth2UserInfo = {
|
|
21
|
+
raw?: any;
|
|
22
|
+
providerUserId: string;
|
|
23
|
+
/** Will take the first option available */
|
|
24
|
+
nameOptions: string[];
|
|
25
|
+
};
|
|
26
|
+
export type KitOAuth2Provider<LitName extends string = string, T extends ArcticOAuth2Provider | ArcticOAuth2ProviderWithPKCE = ArcticOAuth2Provider> = {
|
|
27
|
+
name: LitName;
|
|
28
|
+
getArcticProvider: () => T;
|
|
29
|
+
isPKCE: T extends ArcticOAuth2Provider ? false : T extends ArcticOAuth2ProviderWithPKCE ? true : never;
|
|
30
|
+
authorizationURLOptions: () => T extends ArcticOAuth2Provider ? Parameters<T['createAuthorizationURL']>[1] : T extends ArcticOAuth2ProviderWithPKCE ? Parameters<T['createAuthorizationURL']>[2] : never;
|
|
31
|
+
getUserInfo(tokens: ResolvedType<ReturnType<T['validateAuthorizationCode']>>): Promise<OAuth2UserInfo>;
|
|
32
|
+
};
|
|
33
|
+
type AuthOptions<TUserEntity extends KitAuthUser = KitAuthUser, TSessionEntity extends KitAuthUserSession = KitAuthUserSession, TAccountEntity extends KitAuthAccount = KitAuthAccount> = {
|
|
34
|
+
customEntities?: {
|
|
35
|
+
User?: ClassType<TUserEntity>;
|
|
36
|
+
Session?: ClassType<TSessionEntity>;
|
|
37
|
+
Account?: ClassType<TAccountEntity>;
|
|
38
|
+
};
|
|
39
|
+
ui?: {
|
|
40
|
+
paths?: {
|
|
41
|
+
base?: string;
|
|
42
|
+
};
|
|
43
|
+
} | false;
|
|
44
|
+
/** in secondes @default 15 days */
|
|
45
|
+
sessionExpiresIn?: number;
|
|
46
|
+
sessionCookie?: SessionCookieOptions;
|
|
47
|
+
defaultRedirect?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Can a user sign up by itself? Or we can join only by invitation ?
|
|
50
|
+
* If false, no one can sign up alone.
|
|
51
|
+
* @default true
|
|
52
|
+
**/
|
|
53
|
+
signUp?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* To be able to sign in user needs to be verified or not?
|
|
56
|
+
* ```
|
|
57
|
+
* `Auto` => noting will be checked
|
|
58
|
+
* `Email` => users needs to click a link in an email
|
|
59
|
+
* `Manual` => an admin needs to verify the user and set verifiedAt in the database
|
|
60
|
+
* ```
|
|
61
|
+
* @default auto
|
|
62
|
+
**/
|
|
63
|
+
verifiedMethod?: 'auto' | 'email' | 'manual';
|
|
64
|
+
invitationSend?: (args: {
|
|
65
|
+
email: string;
|
|
66
|
+
url: string;
|
|
67
|
+
}) => Promise<void>;
|
|
68
|
+
providers?: {
|
|
69
|
+
demo?: {
|
|
70
|
+
name: string;
|
|
71
|
+
roles?: string[];
|
|
72
|
+
}[];
|
|
73
|
+
password?: {
|
|
74
|
+
/**
|
|
75
|
+
* Reseting the password
|
|
76
|
+
*/
|
|
77
|
+
resetPasswordSend?: (args: {
|
|
78
|
+
email: string;
|
|
79
|
+
url: string;
|
|
80
|
+
}) => Promise<void>;
|
|
81
|
+
/** in secondes @default 5 minutes */
|
|
82
|
+
resetPasswordExpiresIn?: number;
|
|
83
|
+
/**
|
|
84
|
+
* Verify the Mail
|
|
85
|
+
*/
|
|
86
|
+
verifyMailSend?: (args: {
|
|
87
|
+
email: string;
|
|
88
|
+
url: string;
|
|
89
|
+
}) => Promise<void>;
|
|
90
|
+
/** in secondes @default 5 minutes */
|
|
91
|
+
verifyMailExpiresIn?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Some settings for the password hashing algorithm _(using argon2 under the hood)_
|
|
94
|
+
*/
|
|
95
|
+
argon2Settings?: {
|
|
96
|
+
memorySize?: number | undefined;
|
|
97
|
+
iterations?: number | undefined;
|
|
98
|
+
tagLength?: number | undefined;
|
|
99
|
+
parallelism?: number | undefined;
|
|
100
|
+
secret?: ArrayBuffer | undefined;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
otp?: {
|
|
104
|
+
issuer?: string;
|
|
105
|
+
/** in secondes @default 30 seconds */
|
|
106
|
+
expiresIn?: number;
|
|
107
|
+
/** Number of digits @default 6 */
|
|
108
|
+
digits?: number;
|
|
109
|
+
send?: (data: {
|
|
110
|
+
name: string;
|
|
111
|
+
otp: string;
|
|
112
|
+
uri: string;
|
|
113
|
+
}) => Promise<void>;
|
|
114
|
+
};
|
|
115
|
+
oAuths?: KitOAuth2Provider[];
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
export declare let AUTH_OPTIONS: AuthOptions;
|
|
119
|
+
export declare const getSafeOptions: () => {
|
|
120
|
+
User: ClassType<KitAuthUser>;
|
|
121
|
+
Session: ClassType<KitAuthUserSession>;
|
|
122
|
+
Account: ClassType<KitAuthAccount>;
|
|
123
|
+
signUp: boolean;
|
|
124
|
+
password_enabled: boolean;
|
|
125
|
+
otp_enabled: boolean;
|
|
126
|
+
verifiedMethod: "email" | "auto" | "manual";
|
|
127
|
+
redirectUrl: string;
|
|
128
|
+
firstlyData: firstlyData;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* To enable authentication in your app in a few lines of code.
|
|
132
|
+
* _Info: index: -777_
|
|
133
|
+
*/
|
|
134
|
+
export declare const auth: (o: AuthOptions) => Module;
|
|
135
|
+
export declare const lucia: Lucia<Record<any, any>, UserInfo>;
|
|
136
|
+
declare module 'lucia' {
|
|
137
|
+
interface Register {
|
|
138
|
+
Lucia: typeof lucia;
|
|
139
|
+
DatabaseSessionAttributes: DatabaseSessionAttributes;
|
|
140
|
+
DatabaseUserAttributes: DatabaseUserAttributes;
|
|
141
|
+
}
|
|
142
|
+
interface DatabaseSessionAttributes {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
interface DatabaseUserAttributes {
|
|
146
|
+
id: string;
|
|
147
|
+
name: string;
|
|
148
|
+
roles: string[];
|
|
149
|
+
session: {
|
|
150
|
+
id: string;
|
|
151
|
+
expiresAt: Date;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { redirect } from '@sveltejs/kit';
|
|
2
|
+
import { DEV } from 'esm-env';
|
|
3
|
+
import { Lucia, TimeSpan } from 'lucia';
|
|
4
|
+
import { remult } from 'remult';
|
|
5
|
+
import { Log, red } from '@kitql/helpers';
|
|
6
|
+
import { read } from '@kitql/internals';
|
|
7
|
+
import { KitRole } from '../';
|
|
8
|
+
import { RemultLuciaAdapter } from './Adapter';
|
|
9
|
+
import { AuthController } from './AuthController';
|
|
10
|
+
import { AuthProvider, KitAuthAccount, KitAuthRole, KitAuthUser, KitAuthUserSession, } from './Entities';
|
|
11
|
+
import { createSession } from './helper';
|
|
12
|
+
import { RoleController } from './RoleController';
|
|
13
|
+
export { KitAuthUser, KitAuthAccount, AuthProvider, KitAuthUserSession } from './Entities';
|
|
14
|
+
export { AuthController } from './AuthController';
|
|
15
|
+
export const logAuth = new Log('firstly | auth');
|
|
16
|
+
export { KitAuthRole } from './Entities';
|
|
17
|
+
export let AUTH_OPTIONS = {};
|
|
18
|
+
export const getSafeOptions = () => {
|
|
19
|
+
const signUp = AUTH_OPTIONS.signUp ?? true;
|
|
20
|
+
const base = AUTH_OPTIONS.ui === false ? 'NO_BASE_PATH' : AUTH_OPTIONS.ui?.paths?.base ?? '/kit/auth';
|
|
21
|
+
const oAuths = AUTH_OPTIONS.providers?.oAuths?.map((o) => {
|
|
22
|
+
return o.name;
|
|
23
|
+
}) ?? [];
|
|
24
|
+
const firstlyData = {
|
|
25
|
+
module: 'auth',
|
|
26
|
+
props: {
|
|
27
|
+
ui: {
|
|
28
|
+
paths: {
|
|
29
|
+
base,
|
|
30
|
+
},
|
|
31
|
+
providers: {
|
|
32
|
+
password: {
|
|
33
|
+
dico: {
|
|
34
|
+
email: 'Email',
|
|
35
|
+
email_placeholder: 'Your email address',
|
|
36
|
+
password: 'Password',
|
|
37
|
+
btn_sign_up: 'Sign up',
|
|
38
|
+
btn_sign_in: 'Sign in',
|
|
39
|
+
forgot_password: 'Forgot your password?',
|
|
40
|
+
send_password_reset_instructions: 'Send password reset instructions',
|
|
41
|
+
back_to_sign_in: 'Back to sign in',
|
|
42
|
+
},
|
|
43
|
+
paths: {
|
|
44
|
+
sign_up: signUp ? `${base}/sign-up` : false,
|
|
45
|
+
sign_in: `${base}/sign-in`,
|
|
46
|
+
forgot_password: `${base}/forgot-password`,
|
|
47
|
+
reset_password: `${base}/reset-password`,
|
|
48
|
+
verify_email: `${base}/verify-email`,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
oAuths,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
let redirectUrl = AUTH_OPTIONS.defaultRedirect ?? '/';
|
|
57
|
+
if (!redirectUrl.startsWith('/')) {
|
|
58
|
+
logAuth.error(`Invalid redirect url ${red(redirectUrl)} (it should be a local one starting with /)`);
|
|
59
|
+
redirectUrl = '/';
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
User: AUTH_OPTIONS.customEntities?.User ?? KitAuthUser,
|
|
63
|
+
Session: AUTH_OPTIONS.customEntities?.Session ?? KitAuthUserSession,
|
|
64
|
+
Account: AUTH_OPTIONS.customEntities?.Account ?? KitAuthAccount,
|
|
65
|
+
signUp,
|
|
66
|
+
password_enabled: AUTH_OPTIONS.providers?.password ? true : false,
|
|
67
|
+
otp_enabled: AUTH_OPTIONS.providers?.otp ? true : false,
|
|
68
|
+
verifiedMethod: AUTH_OPTIONS.verifiedMethod ?? 'auto',
|
|
69
|
+
redirectUrl,
|
|
70
|
+
firstlyData,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* To enable authentication in your app in a few lines of code.
|
|
75
|
+
* _Info: index: -777_
|
|
76
|
+
*/
|
|
77
|
+
export const auth = (o) => {
|
|
78
|
+
AUTH_OPTIONS = o;
|
|
79
|
+
const oSafe = getSafeOptions();
|
|
80
|
+
return {
|
|
81
|
+
name: 'auth',
|
|
82
|
+
index: -777,
|
|
83
|
+
entities: [oSafe.User, oSafe.Session, oSafe.Account],
|
|
84
|
+
controllers: [AuthController],
|
|
85
|
+
initRequest: async (event) => {
|
|
86
|
+
// std session
|
|
87
|
+
const sessionId = event.cookies.get(lucia.sessionCookieName);
|
|
88
|
+
if (sessionId) {
|
|
89
|
+
const { session, user } = await lucia.validateSession(sessionId);
|
|
90
|
+
if (session && session.fresh) {
|
|
91
|
+
const sessionCookie = lucia.createSessionCookie(session.id);
|
|
92
|
+
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
|
93
|
+
path: '/',
|
|
94
|
+
...sessionCookie.attributes,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
remult.user = user ?? undefined;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
earlyReturn: async ({ event, resolve }) => {
|
|
101
|
+
if (AUTH_OPTIONS.ui === false) {
|
|
102
|
+
return { early: false };
|
|
103
|
+
}
|
|
104
|
+
const oSafe = getSafeOptions();
|
|
105
|
+
if (event.url.pathname === oSafe.firstlyData.props.ui.providers.password.paths.verify_email) {
|
|
106
|
+
// TODO need to verify the token and set the verifiedAt in the database and we are good.
|
|
107
|
+
// It's 2 minutes, but I'll be it later :D
|
|
108
|
+
const token = event.url.searchParams.get('token') ?? '';
|
|
109
|
+
if (!oSafe.password_enabled) {
|
|
110
|
+
throw Error('Password is not enabled!');
|
|
111
|
+
}
|
|
112
|
+
const account = await remult
|
|
113
|
+
.repo(oSafe.Account)
|
|
114
|
+
.findFirst({ token, provider: AuthProvider.PASSWORD.id });
|
|
115
|
+
if (!account) {
|
|
116
|
+
throw new Error('Invalid token');
|
|
117
|
+
}
|
|
118
|
+
if (account.expiresAt && account.expiresAt < new Date()) {
|
|
119
|
+
throw new Error('token expired');
|
|
120
|
+
}
|
|
121
|
+
await lucia.invalidateUserSessions(account.userId);
|
|
122
|
+
// update elements
|
|
123
|
+
account.token = undefined;
|
|
124
|
+
account.expiresAt = undefined;
|
|
125
|
+
account.lastVerifiedAt = new Date();
|
|
126
|
+
await remult.repo(oSafe.Account).save(account);
|
|
127
|
+
await createSession(account.userId);
|
|
128
|
+
redirect(302, oSafe.redirectUrl);
|
|
129
|
+
}
|
|
130
|
+
if (event.url.pathname.startsWith(oSafe.firstlyData.props.ui.paths.base)) {
|
|
131
|
+
return {
|
|
132
|
+
early: true,
|
|
133
|
+
resolve: new Response(read('src/lib/auth/static/index.html') +
|
|
134
|
+
`<script>const firstlyData = ${JSON.stringify(oSafe.firstlyData)}</script>`, {
|
|
135
|
+
headers: { 'content-type': 'text/html' },
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (event.url.pathname.startsWith('/api/static')) {
|
|
140
|
+
const content = read(`src/lib/auth/static/${event.url.pathname.replaceAll('/api/static/', '')}`);
|
|
141
|
+
if (content) {
|
|
142
|
+
const seg = event.url.pathname.split('.');
|
|
143
|
+
const map = {
|
|
144
|
+
js: 'text/javascript',
|
|
145
|
+
css: 'text/css',
|
|
146
|
+
svg: 'image/svg+xml',
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
early: true,
|
|
150
|
+
resolve: new Response(content, {
|
|
151
|
+
headers: { 'content-type': map[seg[seg.length - 1]] ?? 'text/plain' },
|
|
152
|
+
}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (event.url.pathname === '/api/auth_callback') {
|
|
157
|
+
const code = event.url.searchParams.get('code');
|
|
158
|
+
const state = event.url.searchParams.get('state');
|
|
159
|
+
const keys = AUTH_OPTIONS.providers?.oAuths?.map((c) => c.name) ?? [];
|
|
160
|
+
let storedState = null;
|
|
161
|
+
let keyState = null;
|
|
162
|
+
for (const key of keys) {
|
|
163
|
+
storedState = event.cookies.get(`${key}_oauth_state`) ?? null;
|
|
164
|
+
if (storedState) {
|
|
165
|
+
keyState = key;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const redirectUrlCookie = event.cookies.get(`remult_redirect`);
|
|
170
|
+
if (redirectUrlCookie) {
|
|
171
|
+
event.cookies.delete(`remult_redirect`, { path: '/' });
|
|
172
|
+
}
|
|
173
|
+
const redirectUrl = redirectUrlCookie ?? oSafe.redirectUrl;
|
|
174
|
+
if (!code || !state || !storedState || state !== storedState || !keyState) {
|
|
175
|
+
redirect(302, redirectUrl);
|
|
176
|
+
}
|
|
177
|
+
const selectedOAuth = AUTH_OPTIONS.providers?.oAuths?.find((c) => c.name === keyState);
|
|
178
|
+
if (selectedOAuth && code) {
|
|
179
|
+
const tokens = await selectedOAuth.getArcticProvider().validateAuthorizationCode(code);
|
|
180
|
+
let info;
|
|
181
|
+
try {
|
|
182
|
+
info = await selectedOAuth.getUserInfo(tokens);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
redirect(302, redirectUrl);
|
|
186
|
+
}
|
|
187
|
+
if (!info.providerUserId) {
|
|
188
|
+
redirect(302, redirectUrl);
|
|
189
|
+
}
|
|
190
|
+
let account = await remult
|
|
191
|
+
.repo(oSafe.Account)
|
|
192
|
+
.findFirst({ provider: keyState, providerUserId: info.providerUserId });
|
|
193
|
+
if (!account) {
|
|
194
|
+
if (!oSafe.signUp) {
|
|
195
|
+
// throw Error("You can't signup by yourself! Contact the administrator.")
|
|
196
|
+
redirect(302, redirectUrl);
|
|
197
|
+
}
|
|
198
|
+
// for each info.name, we check if it exists take the first option
|
|
199
|
+
// and add the providerUserId to the name if no option available
|
|
200
|
+
let nameToUse = '';
|
|
201
|
+
for (let i = 0; i < info.nameOptions.length; i++) {
|
|
202
|
+
const existingUser = await remult
|
|
203
|
+
.repo(oSafe.User)
|
|
204
|
+
.findOne({ where: { name: info.nameOptions[i] } });
|
|
205
|
+
if (existingUser) {
|
|
206
|
+
// Don't do anything
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
nameToUse = info.nameOptions[i];
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (nameToUse === '') {
|
|
214
|
+
nameToUse = `${info.nameOptions[0]}-${info.providerUserId}`;
|
|
215
|
+
}
|
|
216
|
+
const user = remult.repo(oSafe.User).create();
|
|
217
|
+
user.name = nameToUse;
|
|
218
|
+
account = remult.repo(oSafe.Account).create();
|
|
219
|
+
account.provider = keyState;
|
|
220
|
+
account.providerUserId = info.providerUserId;
|
|
221
|
+
account.token = tokens.accessToken;
|
|
222
|
+
account.userId = user.id;
|
|
223
|
+
account.lastVerifiedAt = new Date();
|
|
224
|
+
await remult.repo(oSafe.User).save(user);
|
|
225
|
+
await remult.repo(oSafe.Account).save(account);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
account.token = tokens.accessToken;
|
|
229
|
+
await remult.repo(oSafe.Account).save(account);
|
|
230
|
+
}
|
|
231
|
+
await createSession(account.userId);
|
|
232
|
+
event.cookies.delete(`${keyState}_oauth_state`, { path: '/' });
|
|
233
|
+
event.cookies.delete(`code_verifier`, { path: '/' });
|
|
234
|
+
}
|
|
235
|
+
redirect(302, redirectUrl);
|
|
236
|
+
}
|
|
237
|
+
return { early: false };
|
|
238
|
+
},
|
|
239
|
+
initApi: async () => {
|
|
240
|
+
await RoleController.initRoleFromEnv(logAuth, oSafe.User, 'KIT_ADMIN', KitRole.Admin);
|
|
241
|
+
await RoleController.initRoleFromEnv(logAuth, oSafe.User, 'KIT_AUTH_ADMIN', KitAuthRole.Admin);
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
const adapter = new RemultLuciaAdapter();
|
|
246
|
+
const defaultExpiresIn = 60 * 60 * 24 * 15; // 15 days
|
|
247
|
+
export const lucia = new Lucia(adapter, {
|
|
248
|
+
sessionExpiresIn: new TimeSpan(AUTH_OPTIONS.sessionExpiresIn ?? defaultExpiresIn, 's'),
|
|
249
|
+
sessionCookie: {
|
|
250
|
+
name: AUTH_OPTIONS.sessionCookie?.name ?? 'remult_auth_session',
|
|
251
|
+
expires: AUTH_OPTIONS.sessionCookie?.expires,
|
|
252
|
+
attributes: {
|
|
253
|
+
// set to `true` when using HTTPS
|
|
254
|
+
secure: !DEV,
|
|
255
|
+
...AUTH_OPTIONS.sessionCookie?.attributes,
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
getSessionAttributes: (attributes) => {
|
|
259
|
+
return {
|
|
260
|
+
...attributes,
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
getUserAttributes(attributes) {
|
|
264
|
+
// @ts-expect-error
|
|
265
|
+
delete attributes['createdAt'];
|
|
266
|
+
// @ts-expect-error
|
|
267
|
+
delete attributes['updatedAt'];
|
|
268
|
+
// to remove relations
|
|
269
|
+
for (const key in attributes) {
|
|
270
|
+
if (attributes[key] === undefined) {
|
|
271
|
+
delete attributes[key];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return attributes;
|
|
275
|
+
// return {
|
|
276
|
+
// ...attributes,
|
|
277
|
+
// }
|
|
278
|
+
},
|
|
279
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GitHub } from 'arctic';
|
|
2
|
+
import { type KitOAuth2Provider } from '../';
|
|
3
|
+
/**
|
|
4
|
+
* GitHub OAuth2 provider
|
|
5
|
+
*
|
|
6
|
+
* In GitHub, set your callback url to
|
|
7
|
+
* - dev: `http://localhost:5173/api/auth_callback`
|
|
8
|
+
* - prod: `https://MY_SUPER_SITE/api/auth_callback`
|
|
9
|
+
*
|
|
10
|
+
* In your project add a `.env` file with the following:
|
|
11
|
+
*
|
|
12
|
+
* ```env
|
|
13
|
+
* GITHUB_CLIENT_ID= 'your-client-id'
|
|
14
|
+
* GITHUB_CLIENT_SECRET= 'your-client-secret'
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* _FYI: GITHUB_REDIRECT_URI is optional as auth module will default to "${origin}/api/auth_callback"._
|
|
18
|
+
*/
|
|
19
|
+
export declare function github(options?: {
|
|
20
|
+
GITHUB_CLIENT_ID: string;
|
|
21
|
+
GITHUB_CLIENT_SECRET: string;
|
|
22
|
+
GITHUB_REDIRECT_URI?: string;
|
|
23
|
+
authorizationURLOptions?: ReturnType<KitOAuth2Provider<'github', GitHub>['authorizationURLOptions']>;
|
|
24
|
+
log?: boolean;
|
|
25
|
+
}): KitOAuth2Provider<'github', GitHub>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GitHub } from 'arctic';
|
|
2
|
+
import { remult } from 'remult';
|
|
3
|
+
import { checkOAuthConfig } from '.';
|
|
4
|
+
import { logAuth } from '../';
|
|
5
|
+
/**
|
|
6
|
+
* GitHub OAuth2 provider
|
|
7
|
+
*
|
|
8
|
+
* In GitHub, set your callback url to
|
|
9
|
+
* - dev: `http://localhost:5173/api/auth_callback`
|
|
10
|
+
* - prod: `https://MY_SUPER_SITE/api/auth_callback`
|
|
11
|
+
*
|
|
12
|
+
* In your project add a `.env` file with the following:
|
|
13
|
+
*
|
|
14
|
+
* ```env
|
|
15
|
+
* GITHUB_CLIENT_ID= 'your-client-id'
|
|
16
|
+
* GITHUB_CLIENT_SECRET= 'your-client-secret'
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* _FYI: GITHUB_REDIRECT_URI is optional as auth module will default to "${origin}/api/auth_callback"._
|
|
20
|
+
*/
|
|
21
|
+
export function github(options) {
|
|
22
|
+
const name = 'github';
|
|
23
|
+
const clientID = options?.GITHUB_CLIENT_ID ?? '';
|
|
24
|
+
const secret = options?.GITHUB_CLIENT_SECRET ?? '';
|
|
25
|
+
const urlForKeys = 'https://github.com/settings/developers';
|
|
26
|
+
checkOAuthConfig(name, clientID, secret, urlForKeys, false);
|
|
27
|
+
return {
|
|
28
|
+
name,
|
|
29
|
+
isPKCE: false,
|
|
30
|
+
getArcticProvider: () => {
|
|
31
|
+
const redirectURI = options?.GITHUB_REDIRECT_URI || `${remult.context.url.origin}/api/auth_callback`;
|
|
32
|
+
checkOAuthConfig(name, clientID, secret, urlForKeys, true);
|
|
33
|
+
return new GitHub(clientID, secret, { redirectURI });
|
|
34
|
+
},
|
|
35
|
+
authorizationURLOptions: () => {
|
|
36
|
+
return options?.authorizationURLOptions ?? { scopes: [] };
|
|
37
|
+
},
|
|
38
|
+
getUserInfo: async (tokens) => {
|
|
39
|
+
const res = await fetch('https://api.github.com/user', {
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: `Bearer ${tokens.accessToken}`,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
const user = await res.json();
|
|
45
|
+
if (options?.log) {
|
|
46
|
+
logAuth.info(`user`, user);
|
|
47
|
+
}
|
|
48
|
+
return { raw: user, providerUserId: String(user.id), nameOptions: [user.login] };
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cyan, gray, green, italic, yellow } from '@kitql/helpers';
|
|
2
|
+
import { logAuth } from '..';
|
|
3
|
+
import { mask } from '../../formats/strings';
|
|
4
|
+
export const checkOAuthConfig = (name, clientId, secret, urlForKeys, withThrow) => {
|
|
5
|
+
if (!clientId || !secret) {
|
|
6
|
+
const msg = `Wrong configuration for ${green(name)} provider.
|
|
7
|
+
${italic(`Config used ${gray(`(${'.env'} & ${'inferred'}):`)}`)}
|
|
8
|
+
${yellow('--------------- .env ---------------')}
|
|
9
|
+
${name.toUpperCase()}_CLIENT_ID = '${mask(clientId)}'
|
|
10
|
+
${name.toUpperCase()}_CLIENT_SECRET = '${mask(secret)}'
|
|
11
|
+
${yellow('------------------------------------')}
|
|
12
|
+
Update your configuration to fix this error.
|
|
13
|
+
${gray(`By default, we check ${name.toUpperCase()}_CLIENT_ID and ${name.toUpperCase()}_CLIENT_SECRET.
|
|
14
|
+
But you can also pass your keys as parameters.`)}
|
|
15
|
+
Check ${cyan(urlForKeys)} to generate your keys.
|
|
16
|
+
`;
|
|
17
|
+
if (withThrow) {
|
|
18
|
+
throw new Error(msg);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
logAuth.error(msg);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
export { github } from './github';
|
|
26
|
+
export { strava } from './strava';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Strava } from 'arctic';
|
|
2
|
+
import { type KitOAuth2Provider } from '../';
|
|
3
|
+
/**
|
|
4
|
+
* Strava OAuth2 provider
|
|
5
|
+
*
|
|
6
|
+
* In Strava, set your callback url to
|
|
7
|
+
* - dev: `http://localhost:5173/api/auth_callback`
|
|
8
|
+
* - prod: `https://MY_SUPER_SITE/api/auth_callback`
|
|
9
|
+
*
|
|
10
|
+
* In your project add a `.env` file with the following:
|
|
11
|
+
*
|
|
12
|
+
* ```env
|
|
13
|
+
* STRAVA_CLIENT_ID= 'your-client-id'
|
|
14
|
+
* STRAVA_CLIENT_SECRET= 'your-client-secret'
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* _FYI: STRAVA_REDIRECT_URI is optional as auth module will default to "${origin}/api/auth_callback"._
|
|
18
|
+
*/
|
|
19
|
+
export declare function strava(options?: {
|
|
20
|
+
STRAVA_CLIENT_ID: string;
|
|
21
|
+
STRAVA_CLIENT_SECRET: string;
|
|
22
|
+
STRAVA_REDIRECT_URI?: string;
|
|
23
|
+
authorizationURLOptions?: ReturnType<KitOAuth2Provider<'strava', Strava>['authorizationURLOptions']>;
|
|
24
|
+
log?: boolean;
|
|
25
|
+
}): KitOAuth2Provider<'strava', Strava>;
|