Fixed login required redirect, implemented getSequelize func to avoid

sequelize being used on spa
This commit is contained in:
WardPearce
2026-02-14 17:28:03 +13:00
parent 6f1ab3d3c7
commit 49b9c8fff0
5 changed files with 152 additions and 123 deletions
+4 -3
View File
@@ -1,5 +1,5 @@
import { isOwnBackend } from '$lib/shared';
import { sequelize } from '$lib/server/database';
import { getSequelize } from '$lib/server/database';
import { unsign } from 'cookie-signature';
import { env } from '$env/dynamic/private';
import sodium from 'libsodium-wrappers-sumo';
@@ -17,9 +17,10 @@ export async function handle({ event, resolve }) {
return await resolve(event);
}
const sequelize = getSequelize();
if (!sequelizeAuthenticated) {
await sequelize.sync();
await sequelize.authenticate();
await sequelize.sequelize.sync();
await sequelize.sequelize.authenticate();
sequelizeAuthenticated = true;
}
+6 -3
View File
@@ -1,6 +1,6 @@
import { error } from '@sveltejs/kit';
import { verifySolution } from 'altcha-lib';
import { CaptchaTable } from './database';
import { getSequelize } from './database';
export async function verifyCaptcha(payload: string, key: string, maxUses: number = -1) {
const passedCaptcha = await verifySolution(payload, key, true);
@@ -22,11 +22,14 @@ export async function verifyCaptcha(payload: string, key: string, maxUses: numbe
}
if (maxUses !== -1) {
if ((await CaptchaTable.count({ where: { signature: captchaSignature } })) >= maxUses) {
if (
(await getSequelize().CaptchaTable.count({ where: { signature: captchaSignature } })) >=
maxUses
) {
throw error(400, 'Unsupported payload');
}
await CaptchaTable.create({
await getSequelize().CaptchaTable.create({
signature: captchaSignature,
created: new Date()
});
+121 -90
View File
@@ -1,70 +1,132 @@
import { Sequelize, DataTypes, type Model } from 'sequelize';
import { Sequelize, DataTypes, Model, type ModelCtor } from 'sequelize';
import { env } from '$env/dynamic/private';
export const sequelize = new Sequelize(
// Use sqlite::memory: if not provided.
env.DATABASE_CONNECTION_URI ? env.DATABASE_CONNECTION_URI : 'sqlite::memory:'
);
let UserTable: ModelCtor<Model<any, any>>;
let ChannelSubscriptionTable: ModelCtor<Model<any, any>>;
let CaptchaTable: ModelCtor<Model<any, any>>;
export interface UserTableModel extends Model {
id: string;
username: string;
passwordHash: string;
passwordSalt: string;
created: Date;
decryptionKeySalt: string;
masterKeyCipher: string;
masterKeyNonce: string;
}
let sequelizeInstance: Sequelize | null = null;
export const UserTable = sequelize.define(
'User',
{
// Don't want to make our Sequelize instance always
export function getSequelize(): {
sequelize: Sequelize;
UserTable: ModelCtor<Model<any, any>>;
ChannelSubscriptionTable: ModelCtor<Model<any, any>>;
CaptchaTable: ModelCtor<Model<any, any>>;
} {
if (sequelizeInstance) {
return {
sequelize: sequelizeInstance,
UserTable,
ChannelSubscriptionTable,
CaptchaTable
};
}
sequelizeInstance = new Sequelize(
env.DATABASE_CONNECTION_URI ? env.DATABASE_CONNECTION_URI : 'sqlite::memory:'
);
console.log('sequelizeInstance');
UserTable = sequelizeInstance.define(
'User',
{
id: {
type: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true,
unique: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
passwordHash: {
type: DataTypes.STRING,
allowNull: false
},
passwordSalt: {
type: DataTypes.STRING,
allowNull: false
},
created: {
type: DataTypes.DATE,
allowNull: false
},
decryptionKeySalt: {
type: DataTypes.STRING,
allowNull: false
},
masterKeyCipher: {
type: DataTypes.STRING,
allowNull: false
},
masterKeyNonce: {
type: DataTypes.STRING,
allowNull: false
}
},
{
indexes: [
{
unique: true,
fields: ['id', 'username']
}
]
}
);
ChannelSubscriptionTable = sequelizeInstance.define('Subscriptions', {
id: {
type: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true,
unique: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
primaryKey: true
},
passwordHash: {
channelIdCipher: {
type: DataTypes.STRING,
allowNull: false
},
passwordSalt: {
channelIdNonce: {
type: DataTypes.STRING,
allowNull: false
},
channelNameCipher: {
type: DataTypes.STRING,
allowNull: false
},
channelNameNonce: {
type: DataTypes.STRING,
allowNull: false
},
lastRSSFetch: {
type: DataTypes.DATE,
allowNull: false
}
});
CaptchaTable = sequelizeInstance.define('Captchas', {
signature: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true
},
created: {
type: DataTypes.DATE,
allowNull: false
},
decryptionKeySalt: {
type: DataTypes.STRING,
allowNull: false
},
masterKeyCipher: {
type: DataTypes.STRING,
allowNull: false
},
masterKeyNonce: {
type: DataTypes.STRING,
allowNull: false
}
},
{
indexes: [
{
unique: true,
fields: ['id', 'username']
}
]
}
);
});
UserTable.hasMany(ChannelSubscriptionTable);
return {
sequelize: sequelizeInstance,
UserTable,
ChannelSubscriptionTable,
CaptchaTable
};
}
export interface ChannelSubscriptionModel {
id: string; // Hashed authId with subscription key on client.
@@ -76,44 +138,13 @@ export interface ChannelSubscriptionModel {
userId: string;
}
export const ChannelSubscriptionTable = sequelize.define('Subscriptions', {
id: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true
},
channelIdCipher: {
type: DataTypes.STRING,
allowNull: false
},
channelIdNonce: {
type: DataTypes.STRING,
allowNull: false
},
channelNameCipher: {
type: DataTypes.STRING,
allowNull: false
},
channelNameNonce: {
type: DataTypes.STRING,
allowNull: false
},
lastRSSFetch: {
type: DataTypes.DATE,
allowNull: false
}
});
UserTable.hasMany(ChannelSubscriptionTable);
export const CaptchaTable = sequelize.define('Captchas', {
signature: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true
},
created: {
type: DataTypes.DATE,
allowNull: false
}
});
export interface UserTableModel extends Model {
id: string;
username: string;
passwordHash: string;
passwordSalt: string;
created: Date;
decryptionKeySalt: string;
masterKeyCipher: string;
masterKeyNonce: string;
}
+10 -15
View File
@@ -1,9 +1,4 @@
import {
ChannelSubscriptionTable,
UserTable,
type ChannelSubscriptionModel,
type UserTableModel
} from './database';
import { getSequelize, type ChannelSubscriptionModel, type UserTableModel } from './database';
import { Op } from 'sequelize';
import crypto from 'crypto';
import { error } from '@sveltejs/kit';
@@ -35,11 +30,11 @@ export class User {
}
async delete() {
await UserTable.destroy(this.userWhere);
await getSequelize().UserTable.destroy(this.userWhere);
}
async subscriptionRssUpdated(id: string) {
await ChannelSubscriptionTable.update(
await getSequelize().ChannelSubscriptionTable.update(
{ lastRSSFetch: new Date() },
{
where: {
@@ -51,14 +46,14 @@ export class User {
}
async addSubscription(subscription: Omit<ChannelSubscriptionModel, 'userId'>) {
await ChannelSubscriptionTable.create({
await getSequelize().ChannelSubscriptionTable.create({
...subscription,
UserId: this.id
});
}
async removeSubscription(id: string) {
await ChannelSubscriptionTable.destroy({
await getSequelize().ChannelSubscriptionTable.destroy({
where: {
id
}
@@ -67,7 +62,7 @@ export class User {
async amSubscribed(id: string): Promise<boolean> {
return (
(await ChannelSubscriptionTable.count({
(await getSequelize().ChannelSubscriptionTable.count({
where: {
id
}
@@ -76,7 +71,7 @@ export class User {
}
async subscriptions(): Promise<ChannelSubscriptionModel[]> {
const subscriptions = await ChannelSubscriptionTable.findAll({
const subscriptions = await getSequelize().ChannelSubscriptionTable.findAll({
where: {
userId: this.data.id
}
@@ -117,7 +112,7 @@ export async function createUser(user: CreateUser): Promise<User> {
let userCreated = false;
try {
await UserTable.create(createdUser);
await getSequelize().UserTable.create(createdUser);
userCreated = true;
} catch {
userCreated = false;
@@ -131,7 +126,7 @@ export async function createUser(user: CreateUser): Promise<User> {
}
export async function getUser(identifier: string): Promise<User> {
const user = await UserTable.findOne({
const user = await getSequelize().UserTable.findOne({
where: {
[Op.or]: [{ id: identifier }, { username: identifier }]
}
@@ -145,7 +140,7 @@ export async function getUser(identifier: string): Promise<User> {
}
export async function authenticateUser(username: string, passwordHash: string): Promise<User> {
const user = await UserTable.findOne({
const user = await getSequelize().UserTable.findOne({
where: {
username
}
+11 -12
View File
@@ -22,6 +22,7 @@ import { isYTBackend } from '$lib/misc.js';
import { isOwnBackend } from '$lib/shared/index.js';
export const ssr = false;
export const prerender = false;
export async function load({ url }) {
if (browser) {
@@ -46,18 +47,6 @@ export async function load({ url }) {
}
}
if (!get(instanceStore) && !isYTBackend() && !url.pathname.endsWith('/setup')) {
goto(resolve('/setup', {}), { replaceState: true });
}
if (
isOwnBackend()?.requireAuth &&
!get(rawMasterKeyStore) &&
!url.pathname.endsWith('/internal/login')
) {
goto(resolve('/internal/login', {}), { replaceState: true });
}
const resolvedRoot = resolve('/', {});
if (url.pathname.startsWith(resolvedRoot + '@')) {
const username = url.pathname.substring(resolvedRoot.length).split('/')[0];
@@ -87,4 +76,14 @@ export async function load({ url }) {
}
});
}
const isLoginPage = url.pathname.endsWith('/internal/login');
if (!isLoginPage) {
if (isOwnBackend()?.requireAuth && !get(rawMasterKeyStore)) {
goto(resolve('/internal/login', {}), { replaceState: true });
} else if (!get(instanceStore) && !isYTBackend() && !url.pathname.endsWith('/setup')) {
goto(resolve('/setup', {}), { replaceState: true });
}
}
}