Implemented even more of Materialious account system

This commit is contained in:
WardPearce
2026-02-14 00:37:16 +13:00
parent 084ab75121
commit 9565fe983c
13 changed files with 143 additions and 30 deletions
+3 -2
View File
@@ -1,5 +1,5 @@
import { isOwnBackend } from '$lib/backend';
import { sequelize } from '$lib/backendOnly/database';
import { isOwnBackend } from '$lib/shared';
import { sequelize } from '$lib/server/database';
import { unsign } from 'cookie-signature';
import { env } from '$env/dynamic/private';
@@ -10,6 +10,7 @@ export async function handle({ event, resolve }) {
}
if (!sequelizeAuthenticated) {
await sequelize.sync();
await sequelize.authenticate();
sequelizeAuthenticated = true;
}
+49 -18
View File
@@ -1,28 +1,55 @@
import sodium from 'libsodium-wrappers-sumo';
import { rawSubscriptionKeyStore } from './store';
export type IsOwnBackend = {
builtWithBackend: boolean;
internalAuth: boolean;
requireAuth: boolean;
registrationAllowed: boolean;
};
export async function createUserBackend(username: string, rawPassword: string): Promise<boolean> {
await sodium.ready;
export function isOwnBackend(): IsOwnBackend | null {
if (import.meta.env.VITE_BUILD_WITH_BACKEND !== 'true') return null;
const passwordSalt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
return {
builtWithBackend: true,
internalAuth: import.meta.env.VITE_INTERNAL_AUTH !== 'false',
requireAuth: import.meta.env.VITE_REQUIRE_AUTH !== 'false',
registrationAllowed: import.meta.env.VITE_REGISTRATION_ALLOWED === 'true'
};
const loginHash = sodium.crypto_pwhash(
32,
rawPassword,
passwordSalt,
sodium.crypto_pwhash_OPSLIMIT_SENSITIVE,
sodium.crypto_pwhash_MEMLIMIT_SENSITIVE,
sodium.crypto_pwhash_ALG_DEFAULT
);
const subscriptionPasswordSalt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
const userCreateResp = await fetch('/api/user/create', {
method: 'POST',
body: JSON.stringify({
username: username,
password: {
hash: sodium.to_base64(loginHash),
salt: sodium.to_base64(passwordSalt)
},
subscriptionPasswordSalt: sodium.to_base64(subscriptionPasswordSalt)
})
});
if (!userCreateResp.ok) return false;
const subscriptionRawKey = sodium.crypto_pwhash(
32,
rawPassword,
passwordSalt,
sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
sodium.crypto_pwhash_ALG_DEFAULT
);
rawSubscriptionKeyStore.set(sodium.to_base64(subscriptionRawKey));
return true;
}
export async function backendLogin(username: string, rawPassword: string) {
export async function loginUserBackend(username: string, rawPassword: string): Promise<boolean> {
await sodium.ready;
const passwordSaltsResp = await fetch(`/api/user/${username}/public`);
if (!passwordSaltsResp.ok) return;
if (!passwordSaltsResp.ok) return false;
const passwordSalts = await passwordSaltsResp.json();
@@ -39,11 +66,11 @@ export async function backendLogin(username: string, rawPassword: string) {
method: 'POST',
body: JSON.stringify({
username,
passwordHash: loginHash
passwordHash: sodium.to_base64(loginHash)
})
});
if (!loginResp.ok) return;
if (!loginResp.ok) return false;
const subscriptionRawKey = sodium.crypto_pwhash(
32,
@@ -53,4 +80,8 @@ export async function backendLogin(username: string, rawPassword: string) {
sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
sodium.crypto_pwhash_ALG_DEFAULT
);
rawSubscriptionKeyStore.set(sodium.to_base64(subscriptionRawKey));
return true;
}
+1 -1
View File
@@ -28,7 +28,7 @@ import { page } from '$app/state';
import { Capacitor } from '@capacitor/core';
import { Share } from '@capacitor/share';
import { Clipboard } from '@capacitor/clipboard';
import { isOwnBackend } from './backend';
import { isOwnBackend } from './shared';
export function isMobile(): boolean {
const userAgent = navigator.userAgent;
@@ -67,7 +67,17 @@ export async function createUser(user: CreateUser): Promise<User> {
subscriptionPasswordSalt: user.subscriptionPasswordSalt
};
await UserTable.create(createdUser);
let userCreated = false;
try {
await UserTable.create(createdUser);
userCreated = true;
} catch {
userCreated = false;
}
if (!userCreated) {
throw error(400);
}
return new User(createdUser as UserTableModel);
}
+17
View File
@@ -0,0 +1,17 @@
export type IsOwnBackend = {
builtWithBackend: boolean;
internalAuth: boolean;
requireAuth: boolean;
registrationAllowed: boolean;
};
export function isOwnBackend(): IsOwnBackend | null {
if (import.meta.env.VITE_BUILD_WITH_BACKEND !== 'true') return null;
return {
builtWithBackend: true,
internalAuth: import.meta.env.VITE_INTERNAL_AUTH !== 'false',
requireAuth: import.meta.env.VITE_REQUIRE_AUTH !== 'false',
registrationAllowed: import.meta.env.VITE_REGISTRATION_ALLOWED === 'true'
};
}
+6
View File
@@ -333,6 +333,12 @@ export const engineFallbacksStore: Writable<EngineFallback[]> = persist(
'engineFallbacks'
);
export const rawSubscriptionKeyStore: Writable<string | undefined> = persist(
writable(),
createStorage(),
'rawSubscriptionKey'
);
export const syncPartyPeerStore: Writable<Peer | null> = writable(null);
export const syncPartyConnectionsStore: Writable<DataConnection[] | null> = writable();
+3 -1
View File
@@ -42,7 +42,8 @@
import { isYTBackend, clearCaches, truncate } from '$lib/misc';
import Author from '$lib/components/Author.svelte';
import Toast from '$lib/components/Toast.svelte';
import { isOwnBackend } from '$lib/backend';
import { createUserBackend } from '$lib/backend';
import { isOwnBackend } from '$lib/shared';
let { children } = $props();
@@ -116,6 +117,7 @@
async function login() {
if (isOwnBackend()?.internalAuth) {
await createUserBackend('ward', 'password123');
return;
}
+6 -4
View File
@@ -11,7 +11,8 @@ import {
backendInUseStore,
instanceStore,
interfaceDefaultPage,
isAndroidTvStore
isAndroidTvStore,
rawSubscriptionKeyStore
} from '$lib/store';
import { get, type Writable } from 'svelte/store';
import { Capacitor } from '@capacitor/core';
@@ -28,13 +29,14 @@ export async function load({ url }) {
isAndroidTvStore.set((await androidTv.isAndroidTv()).value);
// Due to race condition with how we set & save persistent store values
// we manually set stores like auth & instance before load.
if (Capacitor.getPlatform() === 'android') {
// Due to race condition with how we set & save persistent store values
// we manually set stores like auth & instance before load.
const preferenceKey: Record<string, Writable<any>> = {
invidiousInstance: instanceStore,
authToken: authStore,
backendInUse: backendInUseStore
backendInUse: backendInUseStore,
rawSubscriptionKey: rawSubscriptionKeyStore
};
for (const [key, store] of Object.entries(preferenceKey)) {
@@ -1,4 +1,4 @@
import { isOwnBackend } from '$lib/backend';
import { isOwnBackend } from '$lib/shared';
import psl from 'psl';
import { env } from '$env/dynamic/private';
@@ -1,7 +1,12 @@
import { getUser } from '$lib/backendOnly/user';
import { getUser } from '$lib/server/user';
import { json } from '@sveltejs/kit';
import { isOwnBackend } from '$lib/shared';
export async function GET({ params }) {
if (!isOwnBackend()?.internalAuth) {
return new Response('', { status: 500 });
}
const user = await getUser(params.userId);
return json(user.publicPasswordSalts);
}
@@ -0,0 +1,34 @@
import { isOwnBackend } from '$lib/shared';
import { createUser } from '$lib/server/user.js';
import { error } from '@sveltejs/kit';
import z from 'zod';
const zUserCreate = z.object({
username: z.string().min(3).max(32),
password: z.object({
hash: z.string().max(255),
salt: z.string().max(255)
}),
subscriptionPasswordSalt: z.string().max(255)
});
export async function POST({ request }) {
if (!isOwnBackend()?.internalAuth || !isOwnBackend()?.registrationAllowed) {
return new Response('', { status: 500 });
}
const userToCreate = zUserCreate.safeParse(await request.json());
if (!userToCreate.success) throw error(400);
await createUser({
username: userToCreate.data.username,
password: {
hash: userToCreate.data.password.hash,
salt: userToCreate.data.password.salt
},
subscriptionPasswordSalt: userToCreate.data.subscriptionPasswordSalt
});
return new Response('');
}
@@ -1,8 +1,9 @@
import { authenticateUser, User } from '$lib/backendOnly/user';
import { authenticateUser } from '$lib/server/user';
import { error } from '@sveltejs/kit';
import z from 'zod';
import { env } from '$env/dynamic/private';
import { sign } from 'cookie-signature';
import { isOwnBackend } from '$lib/shared';
const zUserLogin = z.object({
username: z.string(),
@@ -10,6 +11,10 @@ const zUserLogin = z.object({
});
export async function POST({ request, cookies }) {
if (!isOwnBackend()?.internalAuth) {
return new Response('', { status: 500 });
}
const userLogin = zUserLogin.safeParse(await request.json());
if (!userLogin.success) throw error(401);