@vitnode/core 1.2.0-canary.60 → 1.2.0-canary.61
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/dist/scripts/get-config.js +15 -0
- package/dist/scripts/plugin.js +188 -0
- package/dist/scripts/prepare-database.js +177 -0
- package/dist/scripts/prepare-plugins-files.js +134 -0
- package/dist/scripts/run-interactive-shell-command.js +21 -0
- package/dist/scripts/scripts.js +39 -13
- package/dist/scripts/shared/file-utils.js +203 -0
- package/dist/src/api/adapters/sso/discord.js +1 -77
- package/dist/src/api/adapters/sso/facebook.js +1 -76
- package/dist/src/api/adapters/sso/google.js +1 -87
- package/dist/src/api/config.js +1 -67
- package/dist/src/api/lib/check-plugin-id.js +1 -43
- package/dist/src/api/lib/cron.js +1 -22
- package/dist/src/api/lib/logger-middleware.js +1 -35
- package/dist/src/api/lib/module.js +1 -22
- package/dist/src/api/lib/plugin.js +1 -22
- package/dist/src/api/lib/route.js +1 -27
- package/dist/src/api/lib/with-pagination.js +1 -83
- package/dist/src/api/middlewares/captcha.middleware.js +1 -63
- package/dist/src/api/middlewares/cron-auth.middleware.js +1 -19
- package/dist/src/api/middlewares/global.middleware.js +1 -93
- package/dist/src/api/middlewares/rate-limiter.middleware.js +1 -32
- package/dist/src/api/models/device.js +1 -61
- package/dist/src/api/models/email.js +1 -76
- package/dist/src/api/models/password.js +1 -36
- package/dist/src/api/models/session-admin.js +1 -106
- package/dist/src/api/models/session.js +1 -85
- package/dist/src/api/models/sso.js +1 -120
- package/dist/src/api/models/user/get-user-by-id.js +1 -19
- package/dist/src/api/models/user/sign-in-with-passwords.js +1 -22
- package/dist/src/api/models/user/sign-up.js +1 -73
- package/dist/src/api/models/user.js +1 -8
- package/dist/src/api/modules/admin/admin.module.js +1 -19
- package/dist/src/api/modules/admin/advanced/advanced.admin.module.js +1 -11
- package/dist/src/api/modules/admin/advanced/cron/cron.admin.module.js +1 -12
- package/dist/src/api/modules/admin/advanced/cron/routes/get.route.js +1 -66
- package/dist/src/api/modules/admin/advanced/cron/routes/run.route.js +1 -95
- package/dist/src/api/modules/admin/debug/debug.admin.module.js +1 -10
- package/dist/src/api/modules/admin/debug/routes/logs.route.js +1 -94
- package/dist/src/api/modules/admin/routes/session.route.js +1 -47
- package/dist/src/api/modules/admin/users/routes/list.route.js +1 -79
- package/dist/src/api/modules/admin/users/routes/users.route.js +1 -84
- package/dist/src/api/modules/admin/users/users.admin.module.js +1 -10
- package/dist/src/api/modules/cron/cron/clean.cron.js +1 -24
- package/dist/src/api/modules/cron/cron.module.js +1 -14
- package/dist/src/api/modules/cron/helpers/process-cron-jobs.d.ts.map +1 -1
- package/dist/src/api/modules/cron/helpers/process-cron-jobs.js +1 -76
- package/dist/src/api/modules/cron/routes/cron.route.js +1 -101
- package/dist/src/api/modules/middleware/middleware.module.js +1 -10
- package/dist/src/api/modules/middleware/route.js +1 -49
- package/dist/src/api/modules/users/avatar-color.js +1 -43
- package/dist/src/api/modules/users/routes/change-password.route.js +1 -58
- package/dist/src/api/modules/users/routes/reset-passowrd.route.js +1 -91
- package/dist/src/api/modules/users/routes/session.route.js +1 -46
- package/dist/src/api/modules/users/routes/sign-in.route.js +1 -71
- package/dist/src/api/modules/users/routes/sign-out.route.js +1 -43
- package/dist/src/api/modules/users/routes/sign-up.route.js +1 -77
- package/dist/src/api/modules/users/routes/test.route.js +1 -33
- package/dist/src/api/modules/users/sso/routes/callback.route.js +1 -52
- package/dist/src/api/modules/users/sso/routes/create-url.route.js +1 -36
- package/dist/src/api/modules/users/sso/sso.module.js +1 -12
- package/dist/src/api/modules/users/users.module.js +1 -26
- package/dist/src/api/plugin.js +1 -15
- package/dist/src/app/login/page.js +1 -16
- package/dist/src/app/login/reset-password/page.js +1 -18
- package/dist/src/app/login/sso/[providerId]/page.js +1 -10
- package/dist/src/app/register/page.js +1 -16
- package/dist/src/app_admin/core/advanced/cron/page.js +1 -42
- package/dist/src/app_admin/core/debug/page.js +1 -45
- package/dist/src/app_admin/core/page.js +1 -5
- package/dist/src/app_admin/core/test/page.js +1 -5
- package/dist/src/app_admin/core/users/page.js +1 -38
- package/dist/src/components/avatar.js +1 -14
- package/dist/src/components/confirm-action/confirm-action-alert-dialog.js +1 -42
- package/dist/src/components/confirm-action/content.js +1 -31
- package/dist/src/components/date-format.js +1 -41
- package/dist/src/components/form/auto-form.js +1 -103
- package/dist/src/components/form/common/desc.js +1 -9
- package/dist/src/components/form/common/label.js +1 -18
- package/dist/src/components/form/fields/checkbox.js +1 -37
- package/dist/src/components/form/fields/combobox-async.js +1 -123
- package/dist/src/components/form/fields/combobox.js +1 -87
- package/dist/src/components/form/fields/input.js +1 -39
- package/dist/src/components/form/fields/radio-group.js +1 -50
- package/dist/src/components/form/fields/select.js +1 -58
- package/dist/src/components/form/fields/switch.js +1 -36
- package/dist/src/components/form/fields/textarea.js +1 -36
- package/dist/src/components/i18n-provider.js +1 -43
- package/dist/src/components/logo-vitnode.js +1 -119
- package/dist/src/components/switchers/langs/language-switcher.js +1 -47
- package/dist/src/components/switchers/themes/theme-switcher.js +1 -28
- package/dist/src/components/table/content.js +1 -81
- package/dist/src/components/table/data-table.js +1 -68
- package/dist/src/components/table/order-table-head.js +1 -50
- package/dist/src/components/table/pagination.js +1 -122
- package/dist/src/components/tiptap/extension.js +1 -33
- package/dist/src/components/tiptap/toolbar/actions/alignment-action.js +1 -91
- package/dist/src/components/tiptap/toolbar/actions/bold-action.js +1 -39
- package/dist/src/components/tiptap/toolbar/actions/headings-action.js +1 -93
- package/dist/src/components/tiptap/toolbar/actions/italic-action.js +1 -38
- package/dist/src/components/tiptap/toolbar/actions/list-action.js +1 -61
- package/dist/src/components/tiptap/toolbar/actions/text-format-more/text-format-more.js +1 -59
- package/dist/src/components/tiptap/toolbar/actions/underline-action.js +1 -38
- package/dist/src/components/tiptap/toolbar/actions/undo-redo-actions.js +1 -63
- package/dist/src/components/tiptap/toolbar/actions/utils/tooltip-shortcut.js +1 -11
- package/dist/src/components/tiptap/toolbar/tiptap-toolbar.js +1 -55
- package/dist/src/components/tiptap/toolbar/use-toolbar-editor.js +1 -5
- package/dist/src/components/ui/accordion.js +1 -46
- package/dist/src/components/ui/alert-dialog.js +1 -123
- package/dist/src/components/ui/alert.js +1 -39
- package/dist/src/components/ui/badge.js +1 -28
- package/dist/src/components/ui/button-client.js +1 -57
- package/dist/src/components/ui/button.js +1 -32
- package/dist/src/components/ui/card.js +1 -52
- package/dist/src/components/ui/checkbox.js +1 -20
- package/dist/src/components/ui/collapsible.js +1 -22
- package/dist/src/components/ui/command.js +1 -98
- package/dist/src/components/ui/context-menu.js +1 -140
- package/dist/src/components/ui/dialog.js +1 -189
- package/dist/src/components/ui/drawer.js +1 -83
- package/dist/src/components/ui/dropdown-menu.js +1 -141
- package/dist/src/components/ui/editor-content.js +1 -10
- package/dist/src/components/ui/editor.js +1 -35
- package/dist/src/components/ui/form.js +1 -148
- package/dist/src/components/ui/header-content.js +1 -30
- package/dist/src/components/ui/hover-card.js +1 -29
- package/dist/src/components/ui/input-otp.js +1 -48
- package/dist/src/components/ui/input.js +1 -11
- package/dist/src/components/ui/label.js +1 -12
- package/dist/src/components/ui/loader.js +1 -16
- package/dist/src/components/ui/menubar.js +1 -151
- package/dist/src/components/ui/navigation-menu.js +1 -82
- package/dist/src/components/ui/popover.js +1 -35
- package/dist/src/components/ui/progress.js +1 -19
- package/dist/src/components/ui/radio-group.js +1 -27
- package/dist/src/components/ui/scroll-area.js +1 -33
- package/dist/src/components/ui/select.js +1 -113
- package/dist/src/components/ui/separator.js +1 -14
- package/dist/src/components/ui/sheet.js +1 -92
- package/dist/src/components/ui/sidebar.js +1 -433
- package/dist/src/components/ui/skeleton.js +1 -10
- package/dist/src/components/ui/slider.js +1 -50
- package/dist/src/components/ui/sonner.js +1 -26
- package/dist/src/components/ui/switch.js +1 -16
- package/dist/src/components/ui/table.js +1 -63
- package/dist/src/components/ui/tabs.js +1 -33
- package/dist/src/components/ui/textarea.js +1 -10
- package/dist/src/components/ui/toggle-group.js +1 -45
- package/dist/src/components/ui/toggle.js +1 -34
- package/dist/src/components/ui/tooltip.js +1 -57
- package/dist/src/config.js +1 -4
- package/dist/src/database/admins.js +1 -74
- package/dist/src/database/cron.js +1 -22
- package/dist/src/database/languages.js +1 -52
- package/dist/src/database/logs.js +1 -34
- package/dist/src/database/moderators.js +1 -37
- package/dist/src/database/roles.js +1 -13
- package/dist/src/database/sessions.js +1 -60
- package/dist/src/database/users.js +1 -145
- package/dist/src/drizzle.config.js +1 -67
- package/dist/src/emails/default-template.js +1 -99
- package/dist/src/emails/reset-password.js +1 -132
- package/dist/src/emails/ui/button.js +1 -36
- package/dist/src/emails/ui/card.js +1 -39
- package/dist/src/hooks/use-before-unload.js +1 -30
- package/dist/src/hooks/use-captcha.js +1 -122
- package/dist/src/hooks/use-mobile.js +1 -16
- package/dist/src/lib/api/get-middleware-api.js +1 -13
- package/dist/src/lib/api/get-next-cron-run-date.js +1 -14
- package/dist/src/lib/api/get-next-cron-run-date.test.js +1 -55
- package/dist/src/lib/api/get-session-admin-api.js +1 -19
- package/dist/src/lib/api/get-session-api.js +1 -14
- package/dist/src/lib/api/should-cron-job-run.js +1 -16
- package/dist/src/lib/api/should-cron-job-run.test.js +1 -213
- package/dist/src/lib/api/validate-cron-schedule.d.ts +2 -0
- package/dist/src/lib/api/validate-cron-schedule.d.ts.map +1 -0
- package/dist/src/lib/api/validate-cron-schedule.js +1 -0
- package/dist/src/lib/api/validate-cron-schedule.test.d.ts +2 -0
- package/dist/src/lib/api/validate-cron-schedule.test.d.ts.map +1 -0
- package/dist/src/lib/api/validate-cron-schedule.test.js +1 -0
- package/dist/src/lib/colors.js +1 -140
- package/dist/src/lib/colors.test.js +1 -173
- package/dist/src/lib/config.js +1 -14
- package/dist/src/lib/ctrl-or-command-character.js +1 -5
- package/dist/src/lib/fetcher/cookie-from-string-to-object.js +1 -12
- package/dist/src/lib/fetcher/cookie-from-string-to-object.test.js +1 -78
- package/dist/src/lib/fetcher/core.js +3 -48
- package/dist/src/lib/fetcher/helpers-server.js +1 -18
- package/dist/src/lib/fetcher/helpers.js +1 -16
- package/dist/src/lib/fetcher/helpers.test.js +1 -37
- package/dist/src/lib/fetcher/types.js +1 -1
- package/dist/src/lib/fetcher-client.js +1 -17
- package/dist/src/lib/fetcher.js +1 -33
- package/dist/src/lib/helpers/auto-form.js +1 -98
- package/dist/src/lib/helpers/auto-form.test.js +1 -336
- package/dist/src/lib/navigation.js +1 -11
- package/dist/src/lib/plugin.js +1 -3
- package/dist/src/lib/special-characters.js +1 -4
- package/dist/src/lib/special-characters.test.js +1 -56
- package/dist/src/lib/utils.js +1 -5
- package/dist/src/locales/en.json +308 -0
- package/dist/src/tests/setup.js +1 -6
- package/dist/src/views/admin/layouts/admin-layout.js +1 -73
- package/dist/src/views/admin/layouts/sidebar/nav/item.js +1 -98
- package/dist/src/views/admin/layouts/sidebar/nav/nav.js +1 -60
- package/dist/src/views/admin/layouts/sidebar/sidebar.js +1 -28
- package/dist/src/views/admin/layouts/sidebar/sign-out.js +1 -14
- package/dist/src/views/admin/layouts/user-bar/client.js +1 -69
- package/dist/src/views/admin/layouts/user-bar/user-bar.js +1 -32
- package/dist/src/views/admin/sign-in/sign-in-admin-view.js +1 -27
- package/dist/src/views/admin/views/core/advanced/cron/cron-table-view.js +1 -97
- package/dist/src/views/admin/views/core/advanced/cron/run-action/mutation-api.js +1 -23
- package/dist/src/views/admin/views/core/advanced/cron/run-action/run-action.js +1 -38
- package/dist/src/views/admin/views/core/dashboard/dashboard-admin-view.js +1 -44
- package/dist/src/views/admin/views/core/debug/actions/clear-cache/clear-cache.js +1 -35
- package/dist/src/views/admin/views/core/debug/actions/clear-cache/mutation-api.js +1 -5
- package/dist/src/views/admin/views/core/debug/system-logs/actions/more/content.js +1 -281
- package/dist/src/views/admin/views/core/debug/system-logs/actions/more/more.js +1 -67
- package/dist/src/views/admin/views/core/debug/system-logs/badges/badge-status.js +1 -14
- package/dist/src/views/admin/views/core/debug/system-logs/badges/badge-type-log.js +1 -30
- package/dist/src/views/admin/views/core/debug/system-logs/system-logs-view.js +1 -96
- package/dist/src/views/admin/views/core/test.js +1 -161
- package/dist/src/views/admin/views/core/users/users-admin-view.js +1 -89
- package/dist/src/views/auth/password-reset/change-password-form/form.js +1 -48
- package/dist/src/views/auth/password-reset/change-password-form/mutation-api.js +1 -22
- package/dist/src/views/auth/password-reset/change-password-form/use-form.js +1 -36
- package/dist/src/views/auth/password-reset/form/form.js +1 -104
- package/dist/src/views/auth/password-reset/form/mutation-api.js +1 -21
- package/dist/src/views/auth/password-reset/form/use-form.js +1 -33
- package/dist/src/views/auth/password-reset/password-reset-view.js +1 -37
- package/dist/src/views/auth/sign-in/form/form.js +1 -64
- package/dist/src/views/auth/sign-in/form/mutation-api.js +1 -33
- package/dist/src/views/auth/sign-in/form/use-form.js +1 -38
- package/dist/src/views/auth/sign-in/sign-in-view.js +1 -61
- package/dist/src/views/auth/sign-up/components/password-input.js +1 -96
- package/dist/src/views/auth/sign-up/email-confirmation-view.js +1 -55
- package/dist/src/views/auth/sign-up/form/form.js +1 -89
- package/dist/src/views/auth/sign-up/form/mutation-api.js +1 -30
- package/dist/src/views/auth/sign-up/form/use-form.js +1 -70
- package/dist/src/views/auth/sign-up/sign-up-view.js +1 -65
- package/dist/src/views/auth/sign-up/wrapper.js +1 -22
- package/dist/src/views/auth/sso/buttons/client.js +1 -22
- package/dist/src/views/auth/sso/buttons/mutation-api.js +1 -24
- package/dist/src/views/auth/sso/buttons/sso-buttons.js +1 -56
- package/dist/src/views/auth/sso/callback/callback-sso-view.js +1 -32
- package/dist/src/views/auth/sso/callback/client/client.js +1 -68
- package/dist/src/views/auth/sso/callback/client/mutation-api.js +1 -32
- package/dist/src/views/error/back-button.js +1 -14
- package/dist/src/views/error/error-view.js +1 -58
- package/dist/src/views/error/global-error-view.js +1 -50
- package/dist/src/views/layouts/provider.js +1 -44
- package/dist/src/views/layouts/root-layout.js +1 -25
- package/dist/src/views/layouts/theme/header/header.js +1 -38
- package/dist/src/views/layouts/theme/header/user/auth/auth.js +1 -33
- package/dist/src/views/layouts/theme/header/user/auth/client.js +1 -47
- package/dist/src/views/layouts/theme/header/user/auth/log-out-mutation-api.js +1 -27
- package/dist/src/views/layouts/theme/header/user/user.js +1 -32
- package/dist/src/views/layouts/theme/layout.js +1 -16
- package/dist/src/vitnode.config.js +1 -42
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsup.config.js +18 -0
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +49 -0
- package/eslint.config.mjs +2 -0
- package/package.json +6 -10
- package/dist/src/api/adapters/cron/node-cron.adapter.d.ts +0 -3
- package/dist/src/api/adapters/cron/node-cron.adapter.d.ts.map +0 -1
- package/dist/src/api/adapters/cron/node-cron.adapter.js +0 -11
- package/dist/src/api/adapters/email/nodemailer.d.ts +0 -10
- package/dist/src/api/adapters/email/nodemailer.d.ts.map +0 -1
- package/dist/src/api/adapters/email/nodemailer.js +0 -30
- package/dist/src/api/adapters/email/resend.d.ts +0 -6
- package/dist/src/api/adapters/email/resend.d.ts.map +0 -1
- package/dist/src/api/adapters/email/resend.js +0 -21
|
@@ -1,76 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { HTTPException } from "hono/http-exception";
|
|
3
|
-
import { CONFIG } from "../../lib/config.js";
|
|
4
|
-
export class EmailModel {
|
|
5
|
-
constructor(c){
|
|
6
|
-
this.c = c;
|
|
7
|
-
}
|
|
8
|
-
c;
|
|
9
|
-
async send({ html, replyTo, subject, to, user, content, locale: localeFromArgs }) {
|
|
10
|
-
const core = this.c.get("core");
|
|
11
|
-
const provider = core.email?.adapter;
|
|
12
|
-
if (!provider) {
|
|
13
|
-
throw new HTTPException(500, {
|
|
14
|
-
message: "Email provider not found"
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
const locale = localeFromArgs ?? user?.language ?? "en";
|
|
18
|
-
const pluginIds = [
|
|
19
|
-
"@vitnode/core",
|
|
20
|
-
...this.c.get("core").plugins.map((plugin)=>plugin.id)
|
|
21
|
-
];
|
|
22
|
-
const messagesPromises = pluginIds.map(async (pluginId)=>{
|
|
23
|
-
try {
|
|
24
|
-
const path = `${pluginId}/${locale}.json`;
|
|
25
|
-
const messages = await core.pathToMessages(path);
|
|
26
|
-
return messages.default;
|
|
27
|
-
} catch {
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
const allMessages = await Promise.all(messagesPromises);
|
|
32
|
-
// Optimize: Use Object.assign instead of reduce with spread operator
|
|
33
|
-
// to avoid creating intermediate objects on each iteration
|
|
34
|
-
const messages = Object.assign({}, ...allMessages);
|
|
35
|
-
const htmlContent = html ?? content({
|
|
36
|
-
i18n: {
|
|
37
|
-
locale,
|
|
38
|
-
messages
|
|
39
|
-
},
|
|
40
|
-
templateProps: {
|
|
41
|
-
metadata: {
|
|
42
|
-
...core.metadata,
|
|
43
|
-
url: CONFIG.web.href
|
|
44
|
-
},
|
|
45
|
-
logo: core.email?.logo
|
|
46
|
-
},
|
|
47
|
-
user
|
|
48
|
-
});
|
|
49
|
-
const emailTo = user?.email ?? to;
|
|
50
|
-
if (!emailTo) {
|
|
51
|
-
throw new HTTPException(400, {
|
|
52
|
-
message: "Email address is required"
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
await provider.sendEmail({
|
|
57
|
-
html: await render(htmlContent),
|
|
58
|
-
to: emailTo,
|
|
59
|
-
subject: typeof subject === "function" ? subject({
|
|
60
|
-
i18n: {
|
|
61
|
-
locale,
|
|
62
|
-
messages
|
|
63
|
-
}
|
|
64
|
-
}) : subject,
|
|
65
|
-
replyTo,
|
|
66
|
-
metadata: core.metadata,
|
|
67
|
-
text: await render(htmlContent, {
|
|
68
|
-
plainText: true
|
|
69
|
-
})
|
|
70
|
-
});
|
|
71
|
-
} catch (err) {
|
|
72
|
-
const error = err instanceof Error ? err : new Error("Unknown error from email provider");
|
|
73
|
-
await this.c.get("log").error(`Failed to send email: ${error.message}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
import{render}from"@react-email/components";import{HTTPException}from"hono/http-exception";import{CONFIG}from"../../lib/config.js";export class EmailModel{constructor(c){this.c=c}c;async send({html,replyTo,subject,to,user,content,locale:localeFromArgs}){const core=this.c.get("core");const provider=core.email?.adapter;if(!provider){throw new HTTPException(500,{message:"Email provider not found"})}const locale=localeFromArgs??user?.language??"en";const pluginIds=["@vitnode/core",...this.c.get("core").plugins.map(plugin=>plugin.id)];const messagesPromises=pluginIds.map(async pluginId=>{try{const path=`${pluginId}/${locale}.json`;const messages=await core.pathToMessages(path);return messages.default}catch{return{}}});const allMessages=await Promise.all(messagesPromises);const messages=Object.assign({},...allMessages);const htmlContent=html??content({i18n:{locale,messages},templateProps:{metadata:{...core.metadata,url:CONFIG.web.href},logo:core.email?.logo},user});const emailTo=user?.email??to;if(!emailTo){throw new HTTPException(400,{message:"Email address is required"})}try{await provider.sendEmail({html:await render(htmlContent),to:emailTo,subject:typeof subject==="function"?subject({i18n:{locale,messages}}):subject,replyTo,metadata:core.metadata,text:await render(htmlContent,{plainText:true})})}catch(err){const error=err instanceof Error?err:new Error("Unknown error from email provider");await this.c.get("log").error(`Failed to send email: ${error.message}`)}}}
|
|
@@ -1,36 +1 @@
|
|
|
1
|
-
import crypto from
|
|
2
|
-
export class PasswordModel {
|
|
3
|
-
async encryptPassword(password) {
|
|
4
|
-
return await new Promise((resolve, reject)=>{
|
|
5
|
-
const salt = crypto.randomBytes(8).toString("hex");
|
|
6
|
-
crypto.scrypt(password, salt, 64, (err, derivedKey)=>{
|
|
7
|
-
if (err) reject(err);
|
|
8
|
-
resolve(`${salt}:${derivedKey.toString("hex")}`);
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
async verifyPassword(password, hash) {
|
|
13
|
-
return await new Promise((resolve, reject)=>{
|
|
14
|
-
const [salt, key] = hash.split(":");
|
|
15
|
-
const keyBuffer = Buffer.from(key, "hex");
|
|
16
|
-
crypto.scrypt(password, salt, 64, (err, derivedKey)=>{
|
|
17
|
-
if (err) reject(err);
|
|
18
|
-
if (keyBuffer.length !== derivedKey.length) {
|
|
19
|
-
crypto.timingSafeEqual(derivedKey, derivedKey);
|
|
20
|
-
resolve(false);
|
|
21
|
-
} else {
|
|
22
|
-
const areEqual = crypto.timingSafeEqual(keyBuffer, derivedKey);
|
|
23
|
-
resolve(areEqual);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export class ForgotPasswordTokenModel {
|
|
30
|
-
generateResetToken() {
|
|
31
|
-
return crypto.randomBytes(32).toString("base64url");
|
|
32
|
-
}
|
|
33
|
-
hashResetToken(token) {
|
|
34
|
-
return crypto.createHash("sha256").update(token).digest("hex");
|
|
35
|
-
}
|
|
36
|
-
}
|
|
1
|
+
import crypto from"node:crypto";export class PasswordModel{async encryptPassword(password){return await new Promise((resolve,reject)=>{const salt=crypto.randomBytes(8).toString("hex");crypto.scrypt(password,salt,64,(err,derivedKey)=>{if(err)reject(err);resolve(`${salt}:${derivedKey.toString("hex")}`)})})}async verifyPassword(password,hash){return await new Promise((resolve,reject)=>{const[salt,key]=hash.split(":");const keyBuffer=Buffer.from(key,"hex");crypto.scrypt(password,salt,64,(err,derivedKey)=>{if(err)reject(err);if(keyBuffer.length!==derivedKey.length){crypto.timingSafeEqual(derivedKey,derivedKey);resolve(false)}else{const areEqual=crypto.timingSafeEqual(keyBuffer,derivedKey);resolve(areEqual)}})})}}export class ForgotPasswordTokenModel{generateResetToken(){return crypto.randomBytes(32).toString("base64url")}hashResetToken(token){return crypto.createHash("sha256").update(token).digest("hex")}}
|
|
@@ -1,106 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
3
|
-
import { HTTPException } from "hono/http-exception";
|
|
4
|
-
import { core_admin_permissions, core_admin_sessions } from "../../database/admins.js";
|
|
5
|
-
import { CONFIG } from "../../lib/config.js";
|
|
6
|
-
import { DeviceModel } from "./device.js";
|
|
7
|
-
import { UserModel } from "./user.js";
|
|
8
|
-
export class SessionAdminModel {
|
|
9
|
-
constructor(c){
|
|
10
|
-
this.c = c;
|
|
11
|
-
}
|
|
12
|
-
c;
|
|
13
|
-
async hashToken(token) {
|
|
14
|
-
const encoder = new TextEncoder();
|
|
15
|
-
const data = encoder.encode(token);
|
|
16
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
17
|
-
const bytes = new Uint8Array(hashBuffer);
|
|
18
|
-
// Optimize: Use direct iteration instead of Array.from + map
|
|
19
|
-
let result = "";
|
|
20
|
-
for (const byte of bytes){
|
|
21
|
-
result += byte.toString(16).padStart(2, "0");
|
|
22
|
-
}
|
|
23
|
-
return result;
|
|
24
|
-
}
|
|
25
|
-
async checkIfUserIsAdmin(userId) {
|
|
26
|
-
const user = await new UserModel().getUserById({
|
|
27
|
-
id: userId,
|
|
28
|
-
c: this.c
|
|
29
|
-
});
|
|
30
|
-
if (!user) return false;
|
|
31
|
-
const [permission] = await this.c.get("db").select().from(core_admin_permissions).where(or(eq(core_admin_permissions.userId, user.id), eq(core_admin_permissions.roleId, user.roleId))).limit(1);
|
|
32
|
-
return !!permission;
|
|
33
|
-
}
|
|
34
|
-
async createSessionByUserId(userId) {
|
|
35
|
-
const isAdmin = await this.checkIfUserIsAdmin(userId);
|
|
36
|
-
if (!isAdmin) {
|
|
37
|
-
throw new HTTPException(403, {
|
|
38
|
-
message: "Forbidden"
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
const randomBytes = new Uint8Array(64);
|
|
42
|
-
crypto.getRandomValues(randomBytes);
|
|
43
|
-
// Optimize: Use direct iteration instead of Array.from + map
|
|
44
|
-
let token = "";
|
|
45
|
-
for (const byte of randomBytes){
|
|
46
|
-
token += byte.toString(16).padStart(2, "0");
|
|
47
|
-
}
|
|
48
|
-
const hashedToken = await this.hashToken(token);
|
|
49
|
-
const device = await new DeviceModel(this.c).getDeviceId();
|
|
50
|
-
await this.c.get("db").insert(core_admin_sessions).values({
|
|
51
|
-
token: hashedToken,
|
|
52
|
-
userId,
|
|
53
|
-
expiresAt: new Date(Date.now() + this.c.get("core").authorization.adminCookieExpires),
|
|
54
|
-
deviceId: device.id
|
|
55
|
-
});
|
|
56
|
-
setCookie(this.c, this.c.get("core").authorization.adminCookieName, token, {
|
|
57
|
-
httpOnly: true,
|
|
58
|
-
secure: this.c.get("core").authorization.cookieSecure,
|
|
59
|
-
path: "/",
|
|
60
|
-
expires: new Date(Date.now() + this.c.get("core").authorization.adminCookieExpires),
|
|
61
|
-
domain: CONFIG.web.hostname
|
|
62
|
-
});
|
|
63
|
-
return {
|
|
64
|
-
token
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
async deleteSession() {
|
|
68
|
-
const token = getCookie(this.c, this.c.get("core").authorization.adminCookieName);
|
|
69
|
-
if (!token) return;
|
|
70
|
-
const hashedToken = await this.hashToken(token);
|
|
71
|
-
await this.c.get("db").delete(core_admin_sessions).where(eq(core_admin_sessions.token, hashedToken));
|
|
72
|
-
deleteCookie(this.c, this.c.get("core").authorization.adminCookieName, {
|
|
73
|
-
path: "/",
|
|
74
|
-
domain: CONFIG.web.hostname
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
async getUser() {
|
|
78
|
-
const { authorization } = this.c.get("core");
|
|
79
|
-
const token = getCookie(this.c, authorization.adminCookieName);
|
|
80
|
-
if (!token) return null;
|
|
81
|
-
const device = await new DeviceModel(this.c).getDeviceId();
|
|
82
|
-
if (!device) return null;
|
|
83
|
-
const hashedToken = await this.hashToken(token);
|
|
84
|
-
const [session] = await this.c.get("db").select({
|
|
85
|
-
userId: core_admin_sessions.userId
|
|
86
|
-
}).from(core_admin_sessions).where(and(eq(core_admin_sessions.token, hashedToken), eq(core_admin_sessions.deviceId, device.id), gt(core_admin_sessions.expiresAt, new Date()))).limit(1);
|
|
87
|
-
if (!session) {
|
|
88
|
-
deleteCookie(this.c, this.c.get("core").authorization.adminCookieName, {
|
|
89
|
-
path: "/",
|
|
90
|
-
domain: CONFIG.web.hostname
|
|
91
|
-
});
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
const user = await new UserModel().getUserById({
|
|
95
|
-
id: session.userId,
|
|
96
|
-
c: this.c
|
|
97
|
-
});
|
|
98
|
-
if (!user) return null;
|
|
99
|
-
const isStillAdmin = await this.checkIfUserIsAdmin(user.id);
|
|
100
|
-
if (!isStillAdmin) {
|
|
101
|
-
await this.deleteSession();
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
return user;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
1
|
+
import{and,eq,gt,or}from"drizzle-orm";import{deleteCookie,getCookie,setCookie}from"hono/cookie";import{HTTPException}from"hono/http-exception";import{core_admin_permissions,core_admin_sessions}from"../../database/admins.js";import{CONFIG}from"../../lib/config.js";import{DeviceModel}from"./device.js";import{UserModel}from"./user.js";export class SessionAdminModel{constructor(c){this.c=c}c;async hashToken(token){const encoder=new TextEncoder;const data=encoder.encode(token);const hashBuffer=await crypto.subtle.digest("SHA-256",data);const bytes=new Uint8Array(hashBuffer);let result="";for(const byte of bytes){result+=byte.toString(16).padStart(2,"0")}return result}async checkIfUserIsAdmin(userId){const user=await new UserModel().getUserById({id:userId,c:this.c});if(!user)return false;const[permission]=await this.c.get("db").select().from(core_admin_permissions).where(or(eq(core_admin_permissions.userId,user.id),eq(core_admin_permissions.roleId,user.roleId))).limit(1);return!!permission}async createSessionByUserId(userId){const isAdmin=await this.checkIfUserIsAdmin(userId);if(!isAdmin){throw new HTTPException(403,{message:"Forbidden"})}const randomBytes=new Uint8Array(64);crypto.getRandomValues(randomBytes);let token="";for(const byte of randomBytes){token+=byte.toString(16).padStart(2,"0")}const hashedToken=await this.hashToken(token);const device=await new DeviceModel(this.c).getDeviceId();await this.c.get("db").insert(core_admin_sessions).values({token:hashedToken,userId,expiresAt:new Date(Date.now()+this.c.get("core").authorization.adminCookieExpires),deviceId:device.id});setCookie(this.c,this.c.get("core").authorization.adminCookieName,token,{httpOnly:true,secure:this.c.get("core").authorization.cookieSecure,path:"/",expires:new Date(Date.now()+this.c.get("core").authorization.adminCookieExpires),domain:CONFIG.web.hostname});return{token}}async deleteSession(){const token=getCookie(this.c,this.c.get("core").authorization.adminCookieName);if(!token)return;const hashedToken=await this.hashToken(token);await this.c.get("db").delete(core_admin_sessions).where(eq(core_admin_sessions.token,hashedToken));deleteCookie(this.c,this.c.get("core").authorization.adminCookieName,{path:"/",domain:CONFIG.web.hostname})}async getUser(){const{authorization}=this.c.get("core");const token=getCookie(this.c,authorization.adminCookieName);if(!token)return null;const device=await new DeviceModel(this.c).getDeviceId();if(!device)return null;const hashedToken=await this.hashToken(token);const[session]=await this.c.get("db").select({userId:core_admin_sessions.userId}).from(core_admin_sessions).where(and(eq(core_admin_sessions.token,hashedToken),eq(core_admin_sessions.deviceId,device.id),gt(core_admin_sessions.expiresAt,new Date))).limit(1);if(!session){deleteCookie(this.c,this.c.get("core").authorization.adminCookieName,{path:"/",domain:CONFIG.web.hostname});return null}const user=await new UserModel().getUserById({id:session.userId,c:this.c});if(!user)return null;const isStillAdmin=await this.checkIfUserIsAdmin(user.id);if(!isStillAdmin){await this.deleteSession();return null}return user}}
|
|
@@ -1,85 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
3
|
-
import { core_sessions } from "../../database/sessions.js";
|
|
4
|
-
import { CONFIG } from "../../lib/config.js";
|
|
5
|
-
import { DeviceModel } from "./device.js";
|
|
6
|
-
import { UserModel } from "./user.js";
|
|
7
|
-
export class SessionModel {
|
|
8
|
-
constructor(c){
|
|
9
|
-
this.c = c;
|
|
10
|
-
}
|
|
11
|
-
c;
|
|
12
|
-
async hashToken(token) {
|
|
13
|
-
const encoder = new TextEncoder();
|
|
14
|
-
const data = encoder.encode(token);
|
|
15
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
16
|
-
const bytes = new Uint8Array(hashBuffer);
|
|
17
|
-
// Optimize: Use direct iteration instead of Array.from + map
|
|
18
|
-
let result = "";
|
|
19
|
-
for (const byte of bytes){
|
|
20
|
-
result += byte.toString(16).padStart(2, "0");
|
|
21
|
-
}
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
async createSessionByUserId(userId) {
|
|
25
|
-
// Generate secure random bytes using Web Crypto API
|
|
26
|
-
const randomBytes = new Uint8Array(64);
|
|
27
|
-
crypto.getRandomValues(randomBytes);
|
|
28
|
-
// Optimize: Use direct iteration instead of Array.from + map
|
|
29
|
-
let token = "";
|
|
30
|
-
for (const byte of randomBytes){
|
|
31
|
-
token += byte.toString(16).padStart(2, "0");
|
|
32
|
-
}
|
|
33
|
-
const device = await new DeviceModel(this.c).getDeviceId();
|
|
34
|
-
const hashedToken = await this.hashToken(token);
|
|
35
|
-
await this.c.get("db").insert(core_sessions).values({
|
|
36
|
-
token: hashedToken,
|
|
37
|
-
userId,
|
|
38
|
-
expiresAt: new Date(Date.now() + this.c.get("core").authorization.cookie_expires),
|
|
39
|
-
deviceId: device.id
|
|
40
|
-
});
|
|
41
|
-
setCookie(this.c, this.c.get("core").authorization.cookieName, token, {
|
|
42
|
-
httpOnly: true,
|
|
43
|
-
secure: this.c.get("core").authorization.cookieSecure,
|
|
44
|
-
path: "/",
|
|
45
|
-
expires: this.c.get("core").authorization.cookie_expires > 0 ? new Date(Date.now() + this.c.get("core").authorization.cookie_expires) : undefined,
|
|
46
|
-
domain: CONFIG.web.hostname
|
|
47
|
-
});
|
|
48
|
-
return {
|
|
49
|
-
token
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
async deleteSession() {
|
|
53
|
-
const token = getCookie(this.c, this.c.get("core").authorization.cookieName);
|
|
54
|
-
const device = await new DeviceModel(this.c).getDeviceId();
|
|
55
|
-
// Ensure both token and deviceId exist before proceeding
|
|
56
|
-
if (!(token && device.id)) {
|
|
57
|
-
deleteCookie(this.c, this.c.get("core").authorization.cookieName);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const hashedToken = await this.hashToken(token);
|
|
61
|
-
await this.c.get("db").delete(core_sessions)// Harden the query to ensure a user can only delete their own device's session
|
|
62
|
-
.where(and(eq(core_sessions.token, hashedToken), eq(core_sessions.deviceId, device.id)));
|
|
63
|
-
deleteCookie(this.c, this.c.get("core").authorization.cookieName);
|
|
64
|
-
}
|
|
65
|
-
async getUser() {
|
|
66
|
-
const token = getCookie(this.c, this.c.get("core").authorization.cookieName);
|
|
67
|
-
if (!token) return null;
|
|
68
|
-
const device = await new DeviceModel(this.c).getDeviceId();
|
|
69
|
-
if (!device) return null;
|
|
70
|
-
const hashedToken = await this.hashToken(token);
|
|
71
|
-
const [session] = await this.c.get("db").select({
|
|
72
|
-
userId: core_sessions.userId
|
|
73
|
-
}).from(core_sessions).where(and(eq(core_sessions.token, hashedToken), eq(core_sessions.deviceId, device.id), gt(core_sessions.expiresAt, new Date()))).limit(1);
|
|
74
|
-
if (!session) {
|
|
75
|
-
deleteCookie(this.c, this.c.get("core").authorization.cookieName);
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
const user = await new UserModel().getUserById({
|
|
79
|
-
id: session.userId,
|
|
80
|
-
c: this.c
|
|
81
|
-
});
|
|
82
|
-
if (!user) return null;
|
|
83
|
-
return user;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
1
|
+
import{and,eq,gt}from"drizzle-orm";import{deleteCookie,getCookie,setCookie}from"hono/cookie";import{core_sessions}from"../../database/sessions.js";import{CONFIG}from"../../lib/config.js";import{DeviceModel}from"./device.js";import{UserModel}from"./user.js";export class SessionModel{constructor(c){this.c=c}c;async hashToken(token){const encoder=new TextEncoder;const data=encoder.encode(token);const hashBuffer=await crypto.subtle.digest("SHA-256",data);const bytes=new Uint8Array(hashBuffer);let result="";for(const byte of bytes){result+=byte.toString(16).padStart(2,"0")}return result}async createSessionByUserId(userId){const randomBytes=new Uint8Array(64);crypto.getRandomValues(randomBytes);let token="";for(const byte of randomBytes){token+=byte.toString(16).padStart(2,"0")}const device=await new DeviceModel(this.c).getDeviceId();const hashedToken=await this.hashToken(token);await this.c.get("db").insert(core_sessions).values({token:hashedToken,userId,expiresAt:new Date(Date.now()+this.c.get("core").authorization.cookie_expires),deviceId:device.id});setCookie(this.c,this.c.get("core").authorization.cookieName,token,{httpOnly:true,secure:this.c.get("core").authorization.cookieSecure,path:"/",expires:this.c.get("core").authorization.cookie_expires>0?new Date(Date.now()+this.c.get("core").authorization.cookie_expires):undefined,domain:CONFIG.web.hostname});return{token}}async deleteSession(){const token=getCookie(this.c,this.c.get("core").authorization.cookieName);const device=await new DeviceModel(this.c).getDeviceId();if(!(token&&device.id)){deleteCookie(this.c,this.c.get("core").authorization.cookieName);return}const hashedToken=await this.hashToken(token);await this.c.get("db").delete(core_sessions).where(and(eq(core_sessions.token,hashedToken),eq(core_sessions.deviceId,device.id)));deleteCookie(this.c,this.c.get("core").authorization.cookieName)}async getUser(){const token=getCookie(this.c,this.c.get("core").authorization.cookieName);if(!token)return null;const device=await new DeviceModel(this.c).getDeviceId();if(!device)return null;const hashedToken=await this.hashToken(token);const[session]=await this.c.get("db").select({userId:core_sessions.userId}).from(core_sessions).where(and(eq(core_sessions.token,hashedToken),eq(core_sessions.deviceId,device.id),gt(core_sessions.expiresAt,new Date))).limit(1);if(!session){deleteCookie(this.c,this.c.get("core").authorization.cookieName);return null}const user=await new UserModel().getUserById({id:session.userId,c:this.c});if(!user)return null;return user}}
|
|
@@ -1,120 +1 @@
|
|
|
1
|
-
import { and, eq }
|
|
2
|
-
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
3
|
-
import { HTTPException } from "hono/http-exception";
|
|
4
|
-
import crypto from "node:crypto";
|
|
5
|
-
import { core_users, core_users_sso } from "../../database/users.js";
|
|
6
|
-
import { CONFIG } from "../../lib/config.js";
|
|
7
|
-
import { removeSpecialCharacters } from "../../lib/special-characters.js";
|
|
8
|
-
import { UserModel } from "./user.js";
|
|
9
|
-
export const getRedirectUri = (code)=>new URL(`${CONFIG.web.href}login/sso/${code}`).toString();
|
|
10
|
-
export class SSOModel {
|
|
11
|
-
constructor(c){
|
|
12
|
-
this.c = c;
|
|
13
|
-
this.plugins = c.get("core").authorization.ssoAdapters;
|
|
14
|
-
}
|
|
15
|
-
c;
|
|
16
|
-
plugins;
|
|
17
|
-
signUpUser = async ({ providerId, user, c })=>{
|
|
18
|
-
const data = await new UserModel().signUp({
|
|
19
|
-
email: user.email,
|
|
20
|
-
name: removeSpecialCharacters(user.username, false),
|
|
21
|
-
newsletter: false,
|
|
22
|
-
hashedPassword: undefined
|
|
23
|
-
}, c);
|
|
24
|
-
await c.get("db").insert(core_users_sso).values({
|
|
25
|
-
userId: data.id,
|
|
26
|
-
providerId: providerId,
|
|
27
|
-
providerAccountId: user.id
|
|
28
|
-
});
|
|
29
|
-
return {
|
|
30
|
-
userId: data.id
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
async callback({ code, providerId, state }) {
|
|
34
|
-
await this.verifyState(state);
|
|
35
|
-
const provider = this.plugins.find((p)=>p.id === providerId);
|
|
36
|
-
if (!provider) {
|
|
37
|
-
throw new HTTPException(404);
|
|
38
|
-
}
|
|
39
|
-
const ssoToken = await provider.fetchToken(code);
|
|
40
|
-
const userFromSSO = await provider.fetchUser(ssoToken);
|
|
41
|
-
return await this.c.get("db").transaction(async (tx)=>{
|
|
42
|
-
const [dataSSOFromDb] = await tx.select({
|
|
43
|
-
userId: core_users_sso.userId
|
|
44
|
-
}).from(core_users_sso).leftJoin(core_users, eq(core_users.id, core_users_sso.userId)).where(and(eq(core_users_sso.providerId, providerId), eq(core_users_sso.providerAccountId, userFromSSO.id))).limit(1);
|
|
45
|
-
if (!dataSSOFromDb) {
|
|
46
|
-
const [userWithEmail] = await tx.select({
|
|
47
|
-
id: core_users.id,
|
|
48
|
-
email: core_users.email
|
|
49
|
-
}).from(core_users).where(eq(core_users.email, userFromSSO.email)).limit(1);
|
|
50
|
-
if (!userWithEmail) {
|
|
51
|
-
const signUpUser = await this.signUpUser({
|
|
52
|
-
providerId,
|
|
53
|
-
user: userFromSSO,
|
|
54
|
-
c: this.c
|
|
55
|
-
});
|
|
56
|
-
return signUpUser;
|
|
57
|
-
}
|
|
58
|
-
// If email exists, throw an error
|
|
59
|
-
throw new HTTPException(409, {
|
|
60
|
-
message: "Email already exists"
|
|
61
|
-
});
|
|
62
|
-
// await tx.insert(core_users_sso).values({
|
|
63
|
-
// providerId: providerId,
|
|
64
|
-
// providerAccountId: userFromSSO.id,
|
|
65
|
-
// userId: userWithEmail.id,
|
|
66
|
-
// });
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
userId: dataSSOFromDb.userId
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
async encryptState() {
|
|
74
|
-
const state = crypto.randomBytes(8).toString("hex");
|
|
75
|
-
const encryptedState = await new Promise((resolve, reject)=>{
|
|
76
|
-
const salt = crypto.randomBytes(4).toString("hex");
|
|
77
|
-
crypto.scrypt(state, salt, 16, (err, derivedKey)=>{
|
|
78
|
-
if (err) reject(err);
|
|
79
|
-
resolve(`${salt}:${derivedKey.toString("hex")}`);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
setCookie(this.c, `${this.c.get("core").authorization.cookieName}--state-sso`, encryptedState, {
|
|
83
|
-
httpOnly: true,
|
|
84
|
-
secure: this.c.get("core").authorization.cookieSecure,
|
|
85
|
-
path: "/",
|
|
86
|
-
domain: CONFIG.web.hostname
|
|
87
|
-
});
|
|
88
|
-
return state;
|
|
89
|
-
}
|
|
90
|
-
async getUrl(providerId) {
|
|
91
|
-
const provider = this.plugins.find((p)=>p.id === providerId);
|
|
92
|
-
if (!provider) {
|
|
93
|
-
throw new HTTPException(404);
|
|
94
|
-
}
|
|
95
|
-
return provider.getUrl({
|
|
96
|
-
state: await this.encryptState()
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
async verifyState(state) {
|
|
100
|
-
const storedState = getCookie(this.c, `${this.c.get("core").authorization.cookieName}--state-sso`);
|
|
101
|
-
if (!storedState) {
|
|
102
|
-
throw new HTTPException(400, {
|
|
103
|
-
message: "Invalid state"
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
const isValid = await new Promise((resolve, reject)=>{
|
|
107
|
-
const [salt, storedHash] = storedState.split(":");
|
|
108
|
-
crypto.scrypt(state, salt, 16, (err, derivedKey)=>{
|
|
109
|
-
if (err) reject(err);
|
|
110
|
-
resolve(storedHash === derivedKey.toString("hex"));
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
if (!isValid) {
|
|
114
|
-
throw new HTTPException(400, {
|
|
115
|
-
message: "Invalid state"
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
deleteCookie(this.c, `${this.c.get("core").authorization.cookieName}--state-sso`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
1
|
+
import{and,eq}from"drizzle-orm";import{deleteCookie,getCookie,setCookie}from"hono/cookie";import{HTTPException}from"hono/http-exception";import crypto from"node:crypto";import{core_users,core_users_sso}from"../../database/users.js";import{CONFIG}from"../../lib/config.js";import{removeSpecialCharacters}from"../../lib/special-characters.js";import{UserModel}from"./user.js";export const getRedirectUri=code=>new URL(`${CONFIG.web.href}login/sso/${code}`).toString();export class SSOModel{constructor(c){this.c=c;this.plugins=c.get("core").authorization.ssoAdapters}c;plugins;signUpUser=async({providerId,user,c})=>{const data=await new UserModel().signUp({email:user.email,name:removeSpecialCharacters(user.username,false),newsletter:false,hashedPassword:undefined},c);await c.get("db").insert(core_users_sso).values({userId:data.id,providerId:providerId,providerAccountId:user.id});return{userId:data.id}};async callback({code,providerId,state}){await this.verifyState(state);const provider=this.plugins.find(p=>p.id===providerId);if(!provider){throw new HTTPException(404)}const ssoToken=await provider.fetchToken(code);const userFromSSO=await provider.fetchUser(ssoToken);return await this.c.get("db").transaction(async tx=>{const[dataSSOFromDb]=await tx.select({userId:core_users_sso.userId}).from(core_users_sso).leftJoin(core_users,eq(core_users.id,core_users_sso.userId)).where(and(eq(core_users_sso.providerId,providerId),eq(core_users_sso.providerAccountId,userFromSSO.id))).limit(1);if(!dataSSOFromDb){const[userWithEmail]=await tx.select({id:core_users.id,email:core_users.email}).from(core_users).where(eq(core_users.email,userFromSSO.email)).limit(1);if(!userWithEmail){const signUpUser=await this.signUpUser({providerId,user:userFromSSO,c:this.c});return signUpUser}throw new HTTPException(409,{message:"Email already exists"})}return{userId:dataSSOFromDb.userId}})}async encryptState(){const state=crypto.randomBytes(8).toString("hex");const encryptedState=await new Promise((resolve,reject)=>{const salt=crypto.randomBytes(4).toString("hex");crypto.scrypt(state,salt,16,(err,derivedKey)=>{if(err)reject(err);resolve(`${salt}:${derivedKey.toString("hex")}`)})});setCookie(this.c,`${this.c.get("core").authorization.cookieName}--state-sso`,encryptedState,{httpOnly:true,secure:this.c.get("core").authorization.cookieSecure,path:"/",domain:CONFIG.web.hostname});return state}async getUrl(providerId){const provider=this.plugins.find(p=>p.id===providerId);if(!provider){throw new HTTPException(404)}return provider.getUrl({state:await this.encryptState()})}async verifyState(state){const storedState=getCookie(this.c,`${this.c.get("core").authorization.cookieName}--state-sso`);if(!storedState){throw new HTTPException(400,{message:"Invalid state"})}const isValid=await new Promise((resolve,reject)=>{const[salt,storedHash]=storedState.split(":");crypto.scrypt(state,salt,16,(err,derivedKey)=>{if(err)reject(err);resolve(storedHash===derivedKey.toString("hex"))})});if(!isValid){throw new HTTPException(400,{message:"Invalid state"})}deleteCookie(this.c,`${this.c.get("core").authorization.cookieName}--state-sso`)}}
|
|
@@ -1,19 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { core_users } from "../../../database/users.js";
|
|
3
|
-
export const getUserById = async ({ id, c })=>{
|
|
4
|
-
const [user] = await c.get("db").select({
|
|
5
|
-
id: core_users.id,
|
|
6
|
-
email: core_users.email,
|
|
7
|
-
name: core_users.name,
|
|
8
|
-
nameCode: core_users.nameCode,
|
|
9
|
-
createdAt: core_users.createdAt,
|
|
10
|
-
newsletter: core_users.newsletter,
|
|
11
|
-
avatarColor: core_users.avatarColor,
|
|
12
|
-
emailVerified: core_users.emailVerified,
|
|
13
|
-
roleId: core_users.roleId,
|
|
14
|
-
birthday: core_users.birthday,
|
|
15
|
-
language: core_users.language
|
|
16
|
-
}).from(core_users).where(eq(core_users.id, id)).limit(1);
|
|
17
|
-
if (!user) return null;
|
|
18
|
-
return user;
|
|
19
|
-
};
|
|
1
|
+
import{eq}from"drizzle-orm";import{core_users}from"../../../database/users.js";export const getUserById=async({id,c})=>{const[user]=await c.get("db").select({id:core_users.id,email:core_users.email,name:core_users.name,nameCode:core_users.nameCode,createdAt:core_users.createdAt,newsletter:core_users.newsletter,avatarColor:core_users.avatarColor,emailVerified:core_users.emailVerified,roleId:core_users.roleId,birthday:core_users.birthday,language:core_users.language}).from(core_users).where(eq(core_users.id,id)).limit(1);if(!user)return null;return user};
|
|
@@ -1,22 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { HTTPException } from "hono/http-exception";
|
|
3
|
-
import { core_users } from "../../../database/users.js";
|
|
4
|
-
import { PasswordModel } from "../password.js";
|
|
5
|
-
export const signInWithPassword = async ({ email, password, c })=>{
|
|
6
|
-
const [user] = await c.get("db").select({
|
|
7
|
-
id: core_users.id,
|
|
8
|
-
email: core_users.email,
|
|
9
|
-
password: core_users.password
|
|
10
|
-
}).from(core_users).where(eq(core_users.email, email)).limit(1);
|
|
11
|
-
if (!user?.password) {
|
|
12
|
-
throw new HTTPException(403);
|
|
13
|
-
}
|
|
14
|
-
const validPassword = await new PasswordModel().verifyPassword(password, user.password);
|
|
15
|
-
if (!validPassword) {
|
|
16
|
-
throw new HTTPException(403);
|
|
17
|
-
}
|
|
18
|
-
return {
|
|
19
|
-
id: user.id,
|
|
20
|
-
email: user.email
|
|
21
|
-
};
|
|
22
|
-
};
|
|
1
|
+
import{eq}from"drizzle-orm";import{HTTPException}from"hono/http-exception";import{core_users}from"../../../database/users.js";import{PasswordModel}from"../password.js";export const signInWithPassword=async({email,password,c})=>{const[user]=await c.get("db").select({id:core_users.id,email:core_users.email,password:core_users.password}).from(core_users).where(eq(core_users.email,email)).limit(1);if(!user?.password){throw new HTTPException(403)}const validPassword=await new PasswordModel().verifyPassword(password,user.password);if(!validPassword){throw new HTTPException(403)}return{id:user.id,email:user.email}};
|
|
@@ -1,73 +1 @@
|
|
|
1
|
-
import { and,
|
|
2
|
-
import { HTTPException } from "hono/http-exception";
|
|
3
|
-
import { generateAvatarColor } from "../../modules/users/avatar-color.js";
|
|
4
|
-
import { core_roles } from "../../../database/roles.js";
|
|
5
|
-
import { core_users } from "../../../database/users.js";
|
|
6
|
-
import { removeSpecialCharacters } from "../../../lib/special-characters.js";
|
|
7
|
-
const getDefaultData = async (c)=>{
|
|
8
|
-
const [countUsers] = await c.get("db").select({
|
|
9
|
-
count: count()
|
|
10
|
-
}).from(core_users);
|
|
11
|
-
// If no users, return root group
|
|
12
|
-
if (countUsers.count === 0) {
|
|
13
|
-
const [defaultRole] = await c.get("db").select({
|
|
14
|
-
id: core_roles.id
|
|
15
|
-
}).from(core_roles).where(and(eq(core_roles.default, false), eq(core_roles.root, true))).limit(1);
|
|
16
|
-
if (!defaultRole) {
|
|
17
|
-
throw new HTTPException(400, {
|
|
18
|
-
message: "Default group not found."
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
roleId: defaultRole.id,
|
|
23
|
-
emailVerified: true
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
const [defaultRole] = await c.get("db").select({
|
|
27
|
-
id: core_roles.id
|
|
28
|
-
}).from(core_roles).where(and(eq(core_roles.default, true), eq(core_roles.root, false))).limit(1);
|
|
29
|
-
if (!defaultRole) {
|
|
30
|
-
throw new HTTPException(400, {
|
|
31
|
-
message: "Default role not found."
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
roleId: defaultRole.id,
|
|
36
|
-
emailVerified: !c.get("core").email?.adapter
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
export const signUp = async ({ email, name, newsletter, hashedPassword }, c)=>{
|
|
40
|
-
const convertToNameSEO = removeSpecialCharacters(name);
|
|
41
|
-
const checkIfUserExist = await c.get("db").select({
|
|
42
|
-
email: core_users.email,
|
|
43
|
-
name_code: core_users.nameCode
|
|
44
|
-
}).from(core_users).where(or(eq(core_users.email, email), eq(core_users.nameCode, convertToNameSEO)));
|
|
45
|
-
const findEmail = checkIfUserExist.find((user)=>user.email === email);
|
|
46
|
-
if (findEmail) {
|
|
47
|
-
throw new HTTPException(409, {
|
|
48
|
-
message: "Email already exists"
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
const findName = checkIfUserExist.find((user)=>user.name_code === convertToNameSEO);
|
|
52
|
-
if (findName) {
|
|
53
|
-
throw new HTTPException(409, {
|
|
54
|
-
message: "Name already exists"
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
const { roleId, emailVerified } = await getDefaultData(c);
|
|
58
|
-
const [data] = await c.get("db").insert(core_users).values({
|
|
59
|
-
email,
|
|
60
|
-
name,
|
|
61
|
-
nameCode: convertToNameSEO,
|
|
62
|
-
// TODO: Handle newsletter only if email is allowed
|
|
63
|
-
newsletter,
|
|
64
|
-
password: hashedPassword,
|
|
65
|
-
avatarColor: generateAvatarColor(name),
|
|
66
|
-
roleId,
|
|
67
|
-
emailVerified,
|
|
68
|
-
ipAddress: c.get("ipAddress")
|
|
69
|
-
}).returning();
|
|
70
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
71
|
-
const { password: _, ...user } = data;
|
|
72
|
-
return user;
|
|
73
|
-
};
|
|
1
|
+
import{and,count,eq,or}from"drizzle-orm";import{HTTPException}from"hono/http-exception";import{generateAvatarColor}from"../../modules/users/avatar-color.js";import{core_roles}from"../../../database/roles.js";import{core_users}from"../../../database/users.js";import{removeSpecialCharacters}from"../../../lib/special-characters.js";const getDefaultData=async c=>{const[countUsers]=await c.get("db").select({count:count()}).from(core_users);if(countUsers.count===0){const[defaultRole]=await c.get("db").select({id:core_roles.id}).from(core_roles).where(and(eq(core_roles.default,false),eq(core_roles.root,true))).limit(1);if(!defaultRole){throw new HTTPException(400,{message:"Default group not found."})}return{roleId:defaultRole.id,emailVerified:true}}const[defaultRole]=await c.get("db").select({id:core_roles.id}).from(core_roles).where(and(eq(core_roles.default,true),eq(core_roles.root,false))).limit(1);if(!defaultRole){throw new HTTPException(400,{message:"Default role not found."})}return{roleId:defaultRole.id,emailVerified:!c.get("core").email?.adapter}};export const signUp=async({email,name,newsletter,hashedPassword},c)=>{const convertToNameSEO=removeSpecialCharacters(name);const checkIfUserExist=await c.get("db").select({email:core_users.email,name_code:core_users.nameCode}).from(core_users).where(or(eq(core_users.email,email),eq(core_users.nameCode,convertToNameSEO)));const findEmail=checkIfUserExist.find(user=>user.email===email);if(findEmail){throw new HTTPException(409,{message:"Email already exists"})}const findName=checkIfUserExist.find(user=>user.name_code===convertToNameSEO);if(findName){throw new HTTPException(409,{message:"Name already exists"})}const{roleId,emailVerified}=await getDefaultData(c);const[data]=await c.get("db").insert(core_users).values({email,name,nameCode:convertToNameSEO,newsletter,password:hashedPassword,avatarColor:generateAvatarColor(name),roleId,emailVerified,ipAddress:c.get("ipAddress")}).returning();const{password:_,...user}=data;return user};
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { signInWithPassword } from "./user/sign-in-with-passwords.js";
|
|
3
|
-
import { signUp } from "./user/sign-up.js";
|
|
4
|
-
export class UserModel {
|
|
5
|
-
getUserById = getUserById;
|
|
6
|
-
signInWithPassword = signInWithPassword;
|
|
7
|
-
signUp = signUp;
|
|
8
|
-
}
|
|
1
|
+
import{getUserById}from"./user/get-user-by-id.js";import{signInWithPassword}from"./user/sign-in-with-passwords.js";import{signUp}from"./user/sign-up.js";export class UserModel{getUserById=getUserById;signInWithPassword=signInWithPassword;signUp=signUp}
|
|
@@ -1,19 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { CONFIG_PLUGIN } from "../../../config.js";
|
|
3
|
-
import { advancedAdminModule } from "./advanced/advanced.admin.module.js";
|
|
4
|
-
import { debugAdminModule } from "./debug/debug.admin.module.js";
|
|
5
|
-
import { sessionAdminRoute } from "./routes/session.route.js";
|
|
6
|
-
import { usersAdminModule } from "./users/users.admin.module.js";
|
|
7
|
-
export const adminModule = buildModule({
|
|
8
|
-
pluginId: CONFIG_PLUGIN.pluginId,
|
|
9
|
-
name: "admin",
|
|
10
|
-
routes: [
|
|
11
|
-
sessionAdminRoute
|
|
12
|
-
],
|
|
13
|
-
modules: [
|
|
14
|
-
usersAdminModule,
|
|
15
|
-
debugAdminModule,
|
|
16
|
-
advancedAdminModule
|
|
17
|
-
],
|
|
18
|
-
cronJobs: []
|
|
19
|
-
});
|
|
1
|
+
import{buildModule}from"../../lib/module.js";import{CONFIG_PLUGIN}from"../../../config.js";import{advancedAdminModule}from"./advanced/advanced.admin.module.js";import{debugAdminModule}from"./debug/debug.admin.module.js";import{sessionAdminRoute}from"./routes/session.route.js";import{usersAdminModule}from"./users/users.admin.module.js";export const adminModule=buildModule({pluginId:CONFIG_PLUGIN.pluginId,name:"admin",routes:[sessionAdminRoute],modules:[usersAdminModule,debugAdminModule,advancedAdminModule],cronJobs:[]});
|
|
@@ -1,11 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { CONFIG_PLUGIN } from "../../../../config.js";
|
|
3
|
-
import { cronAdminModule } from "./cron/cron.admin.module.js";
|
|
4
|
-
export const advancedAdminModule = buildModule({
|
|
5
|
-
pluginId: CONFIG_PLUGIN.pluginId,
|
|
6
|
-
name: "advanced",
|
|
7
|
-
routes: [],
|
|
8
|
-
modules: [
|
|
9
|
-
cronAdminModule
|
|
10
|
-
]
|
|
11
|
-
});
|
|
1
|
+
import{buildModule}from"../../../lib/module.js";import{CONFIG_PLUGIN}from"../../../../config.js";import{cronAdminModule}from"./cron/cron.admin.module.js";export const advancedAdminModule=buildModule({pluginId:CONFIG_PLUGIN.pluginId,name:"advanced",routes:[],modules:[cronAdminModule]});
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { CONFIG_PLUGIN } from "../../../../../config.js";
|
|
3
|
-
import { getCronsRoute } from "./routes/get.route.js";
|
|
4
|
-
import { runCronRoute } from "./routes/run.route.js";
|
|
5
|
-
export const cronAdminModule = buildModule({
|
|
6
|
-
pluginId: CONFIG_PLUGIN.pluginId,
|
|
7
|
-
name: "cron",
|
|
8
|
-
routes: [
|
|
9
|
-
getCronsRoute,
|
|
10
|
-
runCronRoute
|
|
11
|
-
]
|
|
12
|
-
});
|
|
1
|
+
import{buildModule}from"../../../../lib/module.js";import{CONFIG_PLUGIN}from"../../../../../config.js";import{getCronsRoute}from"./routes/get.route.js";import{runCronRoute}from"./routes/run.route.js";export const cronAdminModule=buildModule({pluginId:CONFIG_PLUGIN.pluginId,name:"cron",routes:[getCronsRoute,runCronRoute]});
|