Hello, ${username}! You have requested a password reset on ${PUBLIC_SITE_NAME}.
+Hello, ${username}! You have requested a password reset on ${env.PUBLIC_SITE_NAME}.
In order to change your password, please click on the following link.
Change your password: ${url}
-If you did not request a password change on ${PUBLIC_SITE_NAME}, you can safely ignore this email.
` +If you did not request a password change on ${env.PUBLIC_SITE_NAME}, you can safely ignore this email.
` }); diff --git a/src/lib/server/email/templates/invitation.email.ts b/src/lib/server/email/templates/invitation.email.ts index 61ec75c..958b0d4 100644 --- a/src/lib/server/email/templates/invitation.email.ts +++ b/src/lib/server/email/templates/invitation.email.ts @@ -1,21 +1,21 @@ -import { PUBLIC_SITE_NAME } from '$env/static/public'; +import { env } from '$env/dynamic/public'; import type { EmailTemplate } from '../template.interface'; export const InvitationEmail = (url: string): EmailTemplate => ({ text: ` -${PUBLIC_SITE_NAME} +${env.PUBLIC_SITE_NAME} -Please click on the following link to create an account on ${PUBLIC_SITE_NAME}. +Please click on the following link to create an account on ${env.PUBLIC_SITE_NAME}. Create your account here: ${url} -This email was sent to you because you have requested an account on ${PUBLIC_SITE_NAME}. If you did not request this, you may safely ignore this email.`, +This email was sent to you because you have requested an account on ${env.PUBLIC_SITE_NAME}. If you did not request this, you may safely ignore this email.`, html: /* html */ ` -Please click on the following link to create an account on ${PUBLIC_SITE_NAME}.
+Please click on the following link to create an account on ${env.PUBLIC_SITE_NAME}.
Create your account here: ${url}
-This email was sent to you because you have requested an account on ${PUBLIC_SITE_NAME}. If you did not request this, you may safely ignore this email.
` +This email was sent to you because you have requested an account on ${env.PUBLIC_SITE_NAME}. If you did not request this, you may safely ignore this email.
` }); diff --git a/src/lib/server/email/templates/oauth2-invitation.email.ts b/src/lib/server/email/templates/oauth2-invitation.email.ts index 4514054..de58bb4 100644 --- a/src/lib/server/email/templates/oauth2-invitation.email.ts +++ b/src/lib/server/email/templates/oauth2-invitation.email.ts @@ -1,4 +1,4 @@ -import { PUBLIC_SITE_NAME } from '$env/static/public'; +import { env } from '$env/dynamic/public'; import type { EmailTemplate } from '../template.interface'; export const OAuth2InvitationEmail = ( @@ -7,23 +7,23 @@ export const OAuth2InvitationEmail = ( url: string ): EmailTemplate => ({ text: ` -${PUBLIC_SITE_NAME} +${env.PUBLIC_SITE_NAME} -${inviter} has invited you to edit the "${clientName}" application on ${PUBLIC_SITE_NAME}. +${inviter} has invited you to edit the "${clientName}" application on ${env.PUBLIC_SITE_NAME}. Please use the following link to accept the invitation. Accept invitation: ${url} -This email was sent to you because someone invited you to contribute to an application on ${PUBLIC_SITE_NAME}. If you believe that this was sent in error, you may safely ignore this email.`, +This email was sent to you because someone invited you to contribute to an application on ${env.PUBLIC_SITE_NAME}. If you believe that this was sent in error, you may safely ignore this email.`, html: /* html */ ` -${inviter} has invited you to edit the "${clientName}" application on ${PUBLIC_SITE_NAME}. +
${inviter} has invited you to edit the "${clientName}" application on ${env.PUBLIC_SITE_NAME}.
Please use the following link to accept the invitation:
Accept invitation: ${url}
-This email was sent to you because someone invited you to contribute to an application on ${PUBLIC_SITE_NAME}. If you believe that this was sent in error, you may safely ignore this email.
` +This email was sent to you because someone invited you to contribute to an application on ${env.PUBLIC_SITE_NAME}. If you believe that this was sent in error, you may safely ignore this email.
` }); diff --git a/src/lib/server/email/templates/registration.email.ts b/src/lib/server/email/templates/registration.email.ts index 23f5b56..673d9e2 100644 --- a/src/lib/server/email/templates/registration.email.ts +++ b/src/lib/server/email/templates/registration.email.ts @@ -1,25 +1,25 @@ -import { PUBLIC_SITE_NAME } from '$env/static/public'; +import { env } from '$env/dynamic/public'; import type { EmailTemplate } from '../template.interface'; export const RegistrationEmail = (username: string, url: string): EmailTemplate => ({ text: ` -${PUBLIC_SITE_NAME} +${env.PUBLIC_SITE_NAME} -Welcome to ${PUBLIC_SITE_NAME}, ${username}! +Welcome to ${env.PUBLIC_SITE_NAME}, ${username}! In order to proceed with logging in, please click on the following link to activate your account. Activate your account: ${url} -This email was sent to you because you have created an account on ${PUBLIC_SITE_NAME}. If you did not create an account, you may contact us or just let the account expire.`, +This email was sent to you because you have created an account on ${env.PUBLIC_SITE_NAME}. If you did not create an account, you may contact us or just let the account expire.`, html: /* html */ ` -Welcome to ${PUBLIC_SITE_NAME}, ${username}!
+Welcome to ${env.PUBLIC_SITE_NAME}, ${username}!
In order to proceed with logging in, please click on the following link to activate your account.
Activate your account: ${url}
-This email was sent to you because you have created an account on ${PUBLIC_SITE_NAME}. If you did not create an account, you may contact us or just let the account expire.
` +This email was sent to you because you have created an account on ${env.PUBLIC_SITE_NAME}. If you did not create an account, you may contact us or just let the account expire.
` }); diff --git a/src/lib/server/jwt.ts b/src/lib/server/jwt.ts index 9b209fb..9da7123 100644 --- a/src/lib/server/jwt.ts +++ b/src/lib/server/jwt.ts @@ -1,4 +1,4 @@ -import { JWT_ALGORITHM, JWT_EXPIRATION, JWT_ISSUER } from '$env/static/private'; +import { env } from '$env/dynamic/private'; import { readFile } from 'fs/promises'; import { SignJWT, @@ -12,6 +12,8 @@ import { import { join } from 'path'; import { v4 as uuidv4 } from 'uuid'; +const { JWT_ALGORITHM, JWT_EXPIRATION, JWT_ISSUER } = env; + /** * Generate JWT keys using the following commands: * Private: openssl genpkey -out jwt.private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048 diff --git a/src/lib/server/oauth2/model/client.ts b/src/lib/server/oauth2/model/client.ts index 43cba96..c7f107e 100644 --- a/src/lib/server/oauth2/model/client.ts +++ b/src/lib/server/oauth2/model/client.ts @@ -1,4 +1,4 @@ -import { PUBLIC_URL, PUBLIC_SITE_NAME } from '$env/static/public'; +import { env } from '$env/dynamic/public'; import { CryptoUtils } from '$lib/server/crypto-utils'; import { DB, @@ -418,14 +418,14 @@ export class OAuth2Clients { const content = OAuth2InvitationEmail( actor.display_name, client.title, - `${PUBLIC_URL}/account/accept-invite?${params.toString()}` + `${env.PUBLIC_URL}/account/accept-invite?${params.toString()}` ); // TODO: logging try { await Emails.getSender().sendTemplate( email, - `You have been invited to manage "${client.title}" on ${PUBLIC_SITE_NAME}`, + `You have been invited to manage "${client.title}" on ${env.PUBLIC_SITE_NAME}`, content ); } catch { diff --git a/src/lib/server/oauth2/model/user.ts b/src/lib/server/oauth2/model/user.ts index 2aa7cc9..431c684 100644 --- a/src/lib/server/oauth2/model/user.ts +++ b/src/lib/server/oauth2/model/user.ts @@ -10,7 +10,7 @@ import { Users } from '$lib/server/users'; import { and, eq } from 'drizzle-orm'; import { OAuth2Clients } from './client'; import { OAuth2Tokens } from './tokens'; -import { PUBLIC_URL } from '$env/static/public'; +import { env } from '$env/dynamic/public'; import { JWT } from '$lib/server/jwt'; export class OAuth2Users { @@ -124,7 +124,7 @@ export class OAuth2Users { } if (scope.includes('picture') && subject.pictureId) { - userData.picture = `${PUBLIC_URL}/api/avatar/${subject.uuid}`; + userData.picture = `${env.PUBLIC_URL}/api/avatar/${subject.uuid}`; } return JWT.issue(userData, subject.uuid, client.client_id); diff --git a/src/lib/server/oauth2/response.ts b/src/lib/server/oauth2/response.ts index 73b2c34..2f8e8f6 100644 --- a/src/lib/server/oauth2/response.ts +++ b/src/lib/server/oauth2/response.ts @@ -73,7 +73,7 @@ export class OAuth2Response { return obj; } - private static createResponse(code: number, data: unknown) { + static createResponse(code: number, data: unknown) { const isJson = typeof data === 'object'; const body = isJson ? JSON.stringify(data) : (data as string); return new Response(body, { @@ -84,7 +84,7 @@ export class OAuth2Response { }); } - private static createErrorResponse(err: OAuth2Error) { + static createErrorResponse(err: OAuth2Error) { return OAuth2Response.createResponse(err.status, { error: err.code, error_description: err.message diff --git a/src/lib/server/users/index.ts b/src/lib/server/users/index.ts index 3f182d2..5d31c52 100644 --- a/src/lib/server/users/index.ts +++ b/src/lib/server/users/index.ts @@ -4,9 +4,9 @@ import { DB, privilege, user, userPrivilegesPrivilege, type User } from '../driz import type { UserSession } from './types'; import { error, redirect } from '@sveltejs/kit'; import { CryptoUtils } from '../crypto-utils'; -import { EMAIL_ENABLED } from '$env/static/private'; +import { env as privateEnv } from '$env/dynamic/private'; import { Emails, ForgotPasswordEmail, InvitationEmail, RegistrationEmail } from '../email'; -import { PUBLIC_SITE_NAME, PUBLIC_URL } from '$env/static/public'; +import { env as publicEnv } from '$env/dynamic/public'; import { UserTokens } from './tokens'; export class Users { @@ -207,13 +207,13 @@ export class Users { username, password: passwordHash, display_name: displayName, - activated: EMAIL_ENABLED === 'false' ? 1 : Number(activate), + activated: privateEnv.EMAIL_ENABLED === 'false' ? 1 : Number(activate), activity_at: new Date() }); const [newUser] = await DB.drizzle.select().from(user).where(eq(user.id, retval.insertId)); - if (EMAIL_ENABLED !== 'false' && !activate) { + if (privateEnv.EMAIL_ENABLED !== 'false' && !activate) { await Users.sendRegistrationEmail(newUser); } @@ -234,13 +234,16 @@ export class Users { ); const params = new URLSearchParams({ activate: token.token }); - const content = RegistrationEmail(user.username, `${PUBLIC_URL}/login?${params.toString()}`); + const content = RegistrationEmail( + user.username, + `${publicEnv.PUBLIC_URL}/login?${params.toString()}` + ); // TODO: logging try { await Emails.getSender().sendTemplate( user.email, - `Activate your account on ${PUBLIC_SITE_NAME}`, + `Activate your account on ${publicEnv.PUBLIC_SITE_NAME}`, content ); } catch (error) { @@ -262,14 +265,14 @@ export class Users { const params = new URLSearchParams({ token: token.token }); const content = ForgotPasswordEmail( user.username, - `${PUBLIC_URL}/login/password?${params.toString()}` + `${publicEnv.PUBLIC_URL}/login/password?${params.toString()}` ); // TODO: logging try { await Emails.getSender().sendTemplate( user.email, - `Reset your password on ${PUBLIC_SITE_NAME}`, + `Reset your password on ${publicEnv.PUBLIC_SITE_NAME}`, content ); } catch { @@ -290,13 +293,13 @@ export class Users { `register=${email}` ); const params = new URLSearchParams({ token: token.token }); - const content = InvitationEmail(`${PUBLIC_URL}/register?${params.toString()}`); + const content = InvitationEmail(`${publicEnv.PUBLIC_URL}/register?${params.toString()}`); // TODO: logging try { await Emails.getSender().sendTemplate( email, - `You have been invited to create an account on ${PUBLIC_SITE_NAME}`, + `You have been invited to create an account on ${publicEnv.PUBLIC_SITE_NAME}`, content ); } catch { diff --git a/src/lib/server/users/totp.ts b/src/lib/server/users/totp.ts index bac21f3..720b0a7 100644 --- a/src/lib/server/users/totp.ts +++ b/src/lib/server/users/totp.ts @@ -1,7 +1,7 @@ import { authenticator as totp } from 'otplib'; import { DB, userToken, type User } from '../drizzle'; import { and, eq, gt, isNull, or } from 'drizzle-orm'; -import { PUBLIC_SITE_NAME } from '$env/static/public'; +import { env } from '$env/dynamic/public'; totp.options = { window: 2 diff --git a/src/lib/theme-mode.ts b/src/lib/theme-mode.ts new file mode 100644 index 0000000..002d154 --- /dev/null +++ b/src/lib/theme-mode.ts @@ -0,0 +1,30 @@ +import { browser } from '$app/environment'; +import { onMount } from 'svelte'; +import { writable } from 'svelte/store'; + +export type ThemeModeType = 'light' | 'dark'; + +export const themeMode = writable{$t('common.description', { siteName: PUBLIC_SITE_NAME })}
+{$t('common.description', { siteName: env.PUBLIC_SITE_NAME })}
{@html $t('common.cookieDisclaimer')}
{PUBLIC_SITE_NAME}
+ {env.PUBLIC_SITE_NAME}
{$t('oauth2.authorize.title')}
diff --git a/src/routes/register/+page.server.ts b/src/routes/register/+page.server.ts
index 10ac81e..ea0f189 100644
--- a/src/routes/register/+page.server.ts
+++ b/src/routes/register/+page.server.ts
@@ -1,4 +1,4 @@
-import { REGISTRATIONS } from '$env/static/private';
+import { env } from '$env/dynamic/private';
import { Changesets } from '$lib/server/changesets.js';
import { Users } from '$lib/server/users/index.js';
import { emailRegex, passwordRegex, usernameRegex } from '$lib/validators.js';
@@ -24,7 +24,7 @@ export const actions = {
if (await limiter.isLimited(event)) throw error(429);
// Logged in users cannot make more accounts
- if (locals.session.data?.user || REGISTRATIONS === 'false') {
+ if (locals.session.data?.user || env.REGISTRATIONS === 'false') {
return redirect(303, '/');
}
@@ -110,6 +110,6 @@ export const load = ({ locals }) => {
}
return {
- enabled: REGISTRATIONS === 'true'
+ enabled: env.REGISTRATIONS === 'true'
};
};
diff --git a/src/routes/register/+page.svelte b/src/routes/register/+page.svelte
index 69d37ad..ca3668e 100644
--- a/src/routes/register/+page.svelte
+++ b/src/routes/register/+page.svelte
@@ -1,5 +1,5 @@
- {$t('account.register.title')} - {PUBLIC_SITE_NAME}
+ {$t('account.register.title')} - {env.PUBLIC_SITE_NAME}
- {PUBLIC_SITE_NAME}
+ {env.PUBLIC_SITE_NAME}
{$t('account.register.title')}
diff --git a/src/routes/ssoadmin/oauth2/+page.svelte b/src/routes/ssoadmin/oauth2/+page.svelte
index 177baa0..6a18364 100644
--- a/src/routes/ssoadmin/oauth2/+page.svelte
+++ b/src/routes/ssoadmin/oauth2/+page.svelte
@@ -5,7 +5,7 @@
import ClientCard from '$lib/components/admin/AdminClientCard.svelte';
import TitleRow from '$lib/components/container/TitleRow.svelte';
import ColumnView from '$lib/components/container/ColumnView.svelte';
- import { PUBLIC_SITE_NAME } from '$env/static/public';
+ import { env } from '$env/dynamic/public';
import FormControl from '$lib/components/form/FormControl.svelte';
import { page } from '$app/stores';
@@ -13,7 +13,7 @@
- {$t('admin.oauth2.title')} - {PUBLIC_SITE_NAME} {$t('admin.title')}
+ {$t('admin.oauth2.title')} - {env.PUBLIC_SITE_NAME} {$t('admin.title')}
diff --git a/src/routes/ssoadmin/oauth2/[uuid]/+page.svelte b/src/routes/ssoadmin/oauth2/[uuid]/+page.svelte
index 6e2977c..3f37815 100644
--- a/src/routes/ssoadmin/oauth2/[uuid]/+page.svelte
+++ b/src/routes/ssoadmin/oauth2/[uuid]/+page.svelte
@@ -15,7 +15,7 @@
import { t } from '$lib/i18n';
import { page } from '$app/stores';
import { writable } from 'svelte/store';
- import { PUBLIC_SITE_NAME, PUBLIC_URL } from '$env/static/public';
+ import { env } from '$env/dynamic/public';
import { OAUTH2_MAX_REDIRECTS, OAUTH2_MAX_URLS } from '$lib/constants';
export let data: PageData;
@@ -43,7 +43,7 @@
{$t('admin.oauth2.title')} / {data.details.title} - {PUBLIC_SITE_NAME}
+ >{$t('admin.oauth2.title')} / {data.details.title} - {env.PUBLIC_SITE_NAME}
{$t('admin.title')}
@@ -293,7 +293,7 @@
{#if data.fullPrivileges || data.details.isOwner}
{$t('admin.oauth2.managers.title')}
- {$t('admin.oauth2.managers.hint', { siteName: PUBLIC_SITE_NAME })}
+ {$t('admin.oauth2.managers.hint', { siteName: env.PUBLIC_SITE_NAME })}
{#each data.managers as user}
@@ -343,7 +343,7 @@
{$t('admin.oauth2.apis.authorize')} -
{PUBLIC_URL}/oauth2/authorize{env.PUBLIC_URL}/oauth2/authorize
@@ -351,7 +351,7 @@
{$t('admin.oauth2.apis.token')} -
{PUBLIC_URL}/oauth2/token{env.PUBLIC_URL}/oauth2/token
@@ -359,21 +359,22 @@
{$t('admin.oauth2.apis.introspect')} -
{PUBLIC_URL}/oauth2/introspect{env.PUBLIC_URL}/oauth2/introspect
{$t('admin.oauth2.apis.userinfo')} -
{PUBLIC_URL}/api/user
{env.PUBLIC_URL}/api/user
{$t('admin.oauth2.apis.openid')} -
{PUBLIC_URL}/.well-known/openid-configuration{env.PUBLIC_URL}/.well-known/openid-configuration
diff --git a/src/routes/ssoadmin/oauth2/[uuid]/user/[user]/+page.svelte b/src/routes/ssoadmin/oauth2/[uuid]/user/[user]/+page.svelte
index 933cd38..44c42fb 100644
--- a/src/routes/ssoadmin/oauth2/[uuid]/user/[user]/+page.svelte
+++ b/src/routes/ssoadmin/oauth2/[uuid]/user/[user]/+page.svelte
@@ -1,5 +1,5 @@
- {$t('admin.oauth2.new')} - {PUBLIC_SITE_NAME} {$t('admin.title')}
+ {$t('admin.oauth2.new')} - {env.PUBLIC_SITE_NAME} {$t('admin.title')}
{$t('admin.oauth2.new')}
diff --git a/src/routes/ssoadmin/users/+page.svelte b/src/routes/ssoadmin/users/+page.svelte
index 85ec557..27920a4 100644
--- a/src/routes/ssoadmin/users/+page.svelte
+++ b/src/routes/ssoadmin/users/+page.svelte
@@ -3,7 +3,7 @@
import { t } from '$lib/i18n';
import type { PageData } from './$types';
import UserCard from '$lib/components/admin/AdminUserCard.svelte';
- import { PUBLIC_SITE_NAME } from '$env/static/public';
+ import { env } from '$env/dynamic/public';
import FormControl from '$lib/components/form/FormControl.svelte';
import ColumnView from '$lib/components/container/ColumnView.svelte';
import { page } from '$app/stores';
@@ -12,7 +12,7 @@
- {$t('admin.users.title')} - {PUBLIC_SITE_NAME} {$t('admin.title')}
+ {$t('admin.users.title')} - {env.PUBLIC_SITE_NAME} {$t('admin.title')}
{$t('admin.users.title')}
diff --git a/src/routes/ssoadmin/users/[uuid]/+page.server.ts b/src/routes/ssoadmin/users/[uuid]/+page.server.ts
index 09097c3..58cafe2 100644
--- a/src/routes/ssoadmin/users/[uuid]/+page.server.ts
+++ b/src/routes/ssoadmin/users/[uuid]/+page.server.ts
@@ -96,7 +96,10 @@ export const actions = {
body
);
- if (!!privileges && !hasPrivileges(userSession.privileges || [], ['admin:user:privilege'])) {
+ if (
+ privileges !== undefined &&
+ !hasPrivileges(userSession.privileges || [], ['admin:user:privilege'])
+ ) {
return fail(403, { errors: ['unauthorized'] });
}
@@ -109,9 +112,15 @@ export const actions = {
return fail(400, { errors: ['lockout'] });
}
- if (privileges) {
- // TODO: check NaNs
- const newPrivilegeIds = privileges?.split(',').map(Number) || [];
+ if (privileges !== undefined) {
+ const newPrivilegeIds =
+ privileges?.split(',').reduce((final, entry) => {
+ if (!entry) return final;
+ const parsed = Number(entry);
+ if (isNaN(parsed) || final.includes(parsed)) return final;
+ return [...final, parsed];
+ }, []) || [];
+
await Users.setUserPrivileges(targetUser, newPrivilegeIds);
}
diff --git a/src/routes/ssoadmin/users/[uuid]/+page.svelte b/src/routes/ssoadmin/users/[uuid]/+page.svelte
index a8ef680..09a0be9 100644
--- a/src/routes/ssoadmin/users/[uuid]/+page.svelte
+++ b/src/routes/ssoadmin/users/[uuid]/+page.svelte
@@ -10,7 +10,7 @@
import type { ActionData, PageData } from './$types';
import AdminPrivilegesSelect from '$lib/components/admin/AdminPrivilegesSelect.svelte';
import FormErrors from '$lib/components/form/FormErrors.svelte';
- import { PUBLIC_SITE_NAME } from '$env/static/public';
+ import { env } from '$env/dynamic/public';
import ActionButton from '$lib/components/ActionButton.svelte';
import Alert from '$lib/components/Alert.svelte';
@@ -20,7 +20,7 @@
{$t('admin.users.title')} / {data.details.display_name} - {PUBLIC_SITE_NAME}
+ >{$t('admin.users.title')} / {data.details.display_name} - {env.PUBLIC_SITE_NAME}
{$t('admin.title')}