Implemented even more of Materialious account system
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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'
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user