firstly 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +18 -0
  3. package/README.md +12 -0
  4. package/esm/KitBaseEnum.d.ts +35 -0
  5. package/esm/KitBaseEnum.js +32 -0
  6. package/esm/KitEntity.d.ts +2 -0
  7. package/esm/KitEntity.js +24 -0
  8. package/esm/KitFields.d.ts +10 -0
  9. package/esm/KitFields.js +196 -0
  10. package/esm/ROUTES.d.ts +88 -0
  11. package/esm/ROUTES.js +98 -0
  12. package/esm/SqlDatabase/LogToConsoleCustom.d.ts +1 -0
  13. package/esm/SqlDatabase/LogToConsoleCustom.js +102 -0
  14. package/esm/api/index.d.ts +42 -0
  15. package/esm/api/index.js +97 -0
  16. package/esm/auth/Adapter.d.ts +10 -0
  17. package/esm/auth/Adapter.js +54 -0
  18. package/esm/auth/AuthController.d.ts +59 -0
  19. package/esm/auth/AuthController.js +434 -0
  20. package/esm/auth/Entities.d.ts +39 -0
  21. package/esm/auth/Entities.js +154 -0
  22. package/esm/auth/RoleController.d.ts +14 -0
  23. package/esm/auth/RoleController.js +57 -0
  24. package/esm/auth/helper.d.ts +1 -0
  25. package/esm/auth/helper.js +7 -0
  26. package/esm/auth/index.d.ts +153 -0
  27. package/esm/auth/index.js +280 -0
  28. package/esm/auth/providers/github.d.ts +25 -0
  29. package/esm/auth/providers/github.js +51 -0
  30. package/esm/auth/providers/index.d.ts +3 -0
  31. package/esm/auth/providers/index.js +26 -0
  32. package/esm/auth/providers/strava.d.ts +25 -0
  33. package/esm/auth/providers/strava.js +51 -0
  34. package/esm/auth/static/assets/Page-BMFREPjF.d.ts +5 -0
  35. package/esm/auth/static/assets/Page-BMFREPjF.js +18 -0
  36. package/esm/auth/static/assets/Page-BMOLAIFx.d.ts +5 -0
  37. package/esm/auth/static/assets/Page-BMOLAIFx.js +1 -0
  38. package/esm/auth/static/assets/Page-BwHye0GW.d.ts +5 -0
  39. package/esm/auth/static/assets/Page-BwHye0GW.js +1 -0
  40. package/esm/auth/static/assets/Page-gV58jf2r.css +1 -0
  41. package/esm/auth/static/assets/index-CKmKKRRL.d.ts +53 -0
  42. package/esm/auth/static/assets/index-CKmKKRRL.js +2 -0
  43. package/esm/auth/static/assets/index-R27C_TlP.css +4 -0
  44. package/esm/auth/static/favicon.svg +79 -0
  45. package/esm/auth/static/index.html +14 -0
  46. package/esm/auth/types.d.ts +33 -0
  47. package/esm/auth/types.js +1 -0
  48. package/esm/bin/cmd.d.ts +1 -0
  49. package/esm/bin/cmd.js +418 -0
  50. package/esm/changeLog/index.d.ts +55 -0
  51. package/esm/changeLog/index.js +179 -0
  52. package/esm/cron/index.d.ts +60 -0
  53. package/esm/cron/index.js +102 -0
  54. package/esm/feedback/FeedbackController.d.ts +30 -0
  55. package/esm/feedback/FeedbackController.js +313 -0
  56. package/esm/feedback/index.d.ts +18 -0
  57. package/esm/feedback/index.js +14 -0
  58. package/esm/feedback/ui/DialogIssue.svelte +102 -0
  59. package/esm/feedback/ui/DialogIssue.svelte.d.ts +20 -0
  60. package/esm/feedback/ui/DialogIssues.svelte +91 -0
  61. package/esm/feedback/ui/DialogIssues.svelte.d.ts +20 -0
  62. package/esm/feedback/ui/DialogMilestones.svelte +38 -0
  63. package/esm/feedback/ui/DialogMilestones.svelte.d.ts +18 -0
  64. package/esm/feedback/ui/Feedback.svelte +12 -0
  65. package/esm/feedback/ui/Feedback.svelte.d.ts +16 -0
  66. package/esm/formats/dates.d.ts +18 -0
  67. package/esm/formats/dates.js +35 -0
  68. package/esm/formats/index.d.ts +4 -0
  69. package/esm/formats/index.js +3 -0
  70. package/esm/formats/numbers.d.ts +4 -0
  71. package/esm/formats/numbers.js +34 -0
  72. package/esm/formats/strings.d.ts +11 -0
  73. package/esm/formats/strings.js +109 -0
  74. package/esm/handle/index.d.ts +7 -0
  75. package/esm/handle/index.js +40 -0
  76. package/esm/helper.d.ts +50 -0
  77. package/esm/helper.js +118 -0
  78. package/esm/index.d.ts +103 -0
  79. package/esm/index.js +42 -0
  80. package/esm/kitCellsBuildor.d.ts +45 -0
  81. package/esm/kitCellsBuildor.js +105 -0
  82. package/esm/kitStoreItem.d.ts +28 -0
  83. package/esm/kitStoreItem.js +170 -0
  84. package/esm/kitStoreList.d.ts +33 -0
  85. package/esm/kitStoreList.js +98 -0
  86. package/esm/mail/index.d.ts +11 -0
  87. package/esm/mail/index.js +51 -0
  88. package/esm/theme.d.ts +4 -0
  89. package/esm/theme.js +4 -0
  90. package/esm/ui/Button.svelte +102 -0
  91. package/esm/ui/Button.svelte.d.ts +27 -0
  92. package/esm/ui/Clipboardable.svelte +19 -0
  93. package/esm/ui/Clipboardable.svelte.d.ts +25 -0
  94. package/esm/ui/Field.svelte +288 -0
  95. package/esm/ui/Field.svelte.d.ts +29 -0
  96. package/esm/ui/FieldGroup.svelte +91 -0
  97. package/esm/ui/FieldGroup.svelte.d.ts +30 -0
  98. package/esm/ui/Grid.svelte +246 -0
  99. package/esm/ui/Grid.svelte.d.ts +46 -0
  100. package/esm/ui/GridLoading.svelte +32 -0
  101. package/esm/ui/GridLoading.svelte.d.ts +20 -0
  102. package/esm/ui/GridPaginate.svelte +66 -0
  103. package/esm/ui/GridPaginate.svelte.d.ts +22 -0
  104. package/esm/ui/Icon.svelte +86 -0
  105. package/esm/ui/Icon.svelte.d.ts +46 -0
  106. package/esm/ui/LibIcon.d.ts +23 -0
  107. package/esm/ui/LibIcon.js +28 -0
  108. package/esm/ui/Loading.svelte +11 -0
  109. package/esm/ui/Loading.svelte.d.ts +20 -0
  110. package/esm/ui/Tooltip.svelte +42 -0
  111. package/esm/ui/Tooltip.svelte.d.ts +22 -0
  112. package/esm/ui/dialog/DialogForm.svelte +70 -0
  113. package/esm/ui/dialog/DialogForm.svelte.d.ts +19 -0
  114. package/esm/ui/dialog/DialogManagement.svelte +87 -0
  115. package/esm/ui/dialog/DialogManagement.svelte.d.ts +25 -0
  116. package/esm/ui/dialog/DialogPrimitive.svelte +89 -0
  117. package/esm/ui/dialog/DialogPrimitive.svelte.d.ts +28 -0
  118. package/esm/ui/dialog/FormEditAction.svelte +54 -0
  119. package/esm/ui/dialog/FormEditAction.svelte.d.ts +24 -0
  120. package/esm/ui/dialog/dialog.d.ts +51 -0
  121. package/esm/ui/dialog/dialog.js +98 -0
  122. package/esm/ui/index.d.ts +5 -0
  123. package/esm/ui/index.js +19 -0
  124. package/esm/ui/internals/FieldContainer.svelte +22 -0
  125. package/esm/ui/internals/FieldContainer.svelte.d.ts +30 -0
  126. package/esm/ui/internals/Input.svelte +98 -0
  127. package/esm/ui/internals/Input.svelte.d.ts +35 -0
  128. package/esm/ui/internals/Textarea.svelte +61 -0
  129. package/esm/ui/internals/Textarea.svelte.d.ts +30 -0
  130. package/esm/ui/internals/select/MultiSelectMelt.svelte +217 -0
  131. package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +30 -0
  132. package/esm/ui/internals/select/SelectMelt.svelte +238 -0
  133. package/esm/ui/internals/select/SelectMelt.svelte.d.ts +35 -0
  134. package/esm/ui/internals/select/SelectRadio.svelte +37 -0
  135. package/esm/ui/internals/select/SelectRadio.svelte.d.ts +25 -0
  136. package/esm/ui/link/Link.svelte +28 -0
  137. package/esm/ui/link/Link.svelte.d.ts +25 -0
  138. package/esm/ui/link/LinkPlus.svelte +44 -0
  139. package/esm/ui/link/LinkPlus.svelte.d.ts +21 -0
  140. package/esm/utils/tailwind.d.ts +2 -0
  141. package/esm/utils/tailwind.js +3 -0
  142. package/esm/utils/transition.d.ts +10 -0
  143. package/esm/utils/transition.js +33 -0
  144. package/esm/utils/types.d.ts +17 -0
  145. package/esm/utils/types.js +17 -0
  146. package/esm/virtual/Customer.d.ts +4 -0
  147. package/esm/virtual/Customer.js +24 -0
  148. package/esm/virtual/FilterEntity.d.ts +7 -0
  149. package/esm/virtual/FilterEntity.js +34 -0
  150. package/esm/virtual/StateDemoEnum.d.ts +9 -0
  151. package/esm/virtual/StateDemoEnum.js +42 -0
  152. package/esm/virtual/UIEntity.d.ts +16 -0
  153. package/esm/virtual/UIEntity.js +84 -0
  154. package/esm/vite/index.d.ts +8 -0
  155. package/esm/vite/index.js +47 -0
  156. package/package.json +94 -10
@@ -0,0 +1,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,280 @@
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
+ const content = read('src/lib/auth/static/index.html') ??
132
+ read('node_modules/firstly/esm/auth/static/index.html');
133
+ return {
134
+ early: true,
135
+ resolve: new Response(content + `<script>const firstlyData = ${JSON.stringify(oSafe.firstlyData)}</script>`, {
136
+ headers: { 'content-type': 'text/html' },
137
+ }),
138
+ };
139
+ }
140
+ if (event.url.pathname.startsWith('/api/static')) {
141
+ const content = read(`src/lib/auth/static/${event.url.pathname.replaceAll('/api/static/', '')}`);
142
+ if (content) {
143
+ const seg = event.url.pathname.split('.');
144
+ const map = {
145
+ js: 'text/javascript',
146
+ css: 'text/css',
147
+ svg: 'image/svg+xml',
148
+ };
149
+ return {
150
+ early: true,
151
+ resolve: new Response(content, {
152
+ headers: { 'content-type': map[seg[seg.length - 1]] ?? 'text/plain' },
153
+ }),
154
+ };
155
+ }
156
+ }
157
+ if (event.url.pathname === '/api/auth_callback') {
158
+ const code = event.url.searchParams.get('code');
159
+ const state = event.url.searchParams.get('state');
160
+ const keys = AUTH_OPTIONS.providers?.oAuths?.map((c) => c.name) ?? [];
161
+ let storedState = null;
162
+ let keyState = null;
163
+ for (const key of keys) {
164
+ storedState = event.cookies.get(`${key}_oauth_state`) ?? null;
165
+ if (storedState) {
166
+ keyState = key;
167
+ break;
168
+ }
169
+ }
170
+ const redirectUrlCookie = event.cookies.get(`remult_redirect`);
171
+ if (redirectUrlCookie) {
172
+ event.cookies.delete(`remult_redirect`, { path: '/' });
173
+ }
174
+ const redirectUrl = redirectUrlCookie ?? oSafe.redirectUrl;
175
+ if (!code || !state || !storedState || state !== storedState || !keyState) {
176
+ redirect(302, redirectUrl);
177
+ }
178
+ const selectedOAuth = AUTH_OPTIONS.providers?.oAuths?.find((c) => c.name === keyState);
179
+ if (selectedOAuth && code) {
180
+ const tokens = await selectedOAuth.getArcticProvider().validateAuthorizationCode(code);
181
+ let info;
182
+ try {
183
+ info = await selectedOAuth.getUserInfo(tokens);
184
+ }
185
+ catch (error) {
186
+ redirect(302, redirectUrl);
187
+ }
188
+ if (!info.providerUserId) {
189
+ redirect(302, redirectUrl);
190
+ }
191
+ let account = await remult
192
+ .repo(oSafe.Account)
193
+ .findFirst({ provider: keyState, providerUserId: info.providerUserId });
194
+ if (!account) {
195
+ if (!oSafe.signUp) {
196
+ // throw Error("You can't signup by yourself! Contact the administrator.")
197
+ redirect(302, redirectUrl);
198
+ }
199
+ // for each info.name, we check if it exists take the first option
200
+ // and add the providerUserId to the name if no option available
201
+ let nameToUse = '';
202
+ for (let i = 0; i < info.nameOptions.length; i++) {
203
+ const existingUser = await remult
204
+ .repo(oSafe.User)
205
+ .findOne({ where: { name: info.nameOptions[i] } });
206
+ if (existingUser) {
207
+ // Don't do anything
208
+ }
209
+ else {
210
+ nameToUse = info.nameOptions[i];
211
+ break;
212
+ }
213
+ }
214
+ if (nameToUse === '') {
215
+ nameToUse = `${info.nameOptions[0]}-${info.providerUserId}`;
216
+ }
217
+ const user = remult.repo(oSafe.User).create();
218
+ user.name = nameToUse;
219
+ account = remult.repo(oSafe.Account).create();
220
+ account.provider = keyState;
221
+ account.providerUserId = info.providerUserId;
222
+ account.token = tokens.accessToken;
223
+ account.userId = user.id;
224
+ account.lastVerifiedAt = new Date();
225
+ await remult.repo(oSafe.User).save(user);
226
+ await remult.repo(oSafe.Account).save(account);
227
+ }
228
+ else {
229
+ account.token = tokens.accessToken;
230
+ await remult.repo(oSafe.Account).save(account);
231
+ }
232
+ await createSession(account.userId);
233
+ event.cookies.delete(`${keyState}_oauth_state`, { path: '/' });
234
+ event.cookies.delete(`code_verifier`, { path: '/' });
235
+ }
236
+ redirect(302, redirectUrl);
237
+ }
238
+ return { early: false };
239
+ },
240
+ initApi: async () => {
241
+ await RoleController.initRoleFromEnv(logAuth, oSafe.User, 'KIT_ADMIN', KitRole.Admin);
242
+ await RoleController.initRoleFromEnv(logAuth, oSafe.User, 'KIT_AUTH_ADMIN', KitAuthRole.Admin);
243
+ },
244
+ };
245
+ };
246
+ const adapter = new RemultLuciaAdapter();
247
+ const defaultExpiresIn = 60 * 60 * 24 * 15; // 15 days
248
+ export const lucia = new Lucia(adapter, {
249
+ sessionExpiresIn: new TimeSpan(AUTH_OPTIONS.sessionExpiresIn ?? defaultExpiresIn, 's'),
250
+ sessionCookie: {
251
+ name: AUTH_OPTIONS.sessionCookie?.name ?? 'remult_auth_session',
252
+ expires: AUTH_OPTIONS.sessionCookie?.expires,
253
+ attributes: {
254
+ // set to `true` when using HTTPS
255
+ secure: !DEV,
256
+ ...AUTH_OPTIONS.sessionCookie?.attributes,
257
+ },
258
+ },
259
+ getSessionAttributes: (attributes) => {
260
+ return {
261
+ ...attributes,
262
+ };
263
+ },
264
+ getUserAttributes(attributes) {
265
+ // @ts-expect-error
266
+ delete attributes['createdAt'];
267
+ // @ts-expect-error
268
+ delete attributes['updatedAt'];
269
+ // to remove relations
270
+ for (const key in attributes) {
271
+ if (attributes[key] === undefined) {
272
+ delete attributes[key];
273
+ }
274
+ }
275
+ return attributes;
276
+ // return {
277
+ // ...attributes,
278
+ // }
279
+ },
280
+ });
@@ -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,3 @@
1
+ export declare const checkOAuthConfig: (name: string, clientId: string, secret: string, urlForKeys: string, withThrow: boolean) => void;
2
+ export { github } from './github';
3
+ export { strava } from './strava';
@@ -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>;