diff --git a/bun.lockb b/bun.lockb index 969a46f..b030e64 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/composables/useDatabase.ts b/composables/useDatabase.ts index af7fd46..124b78c 100644 --- a/composables/useDatabase.ts +++ b/composables/useDatabase.ts @@ -7,7 +7,8 @@ export default function useDatabase() { if(!instance) { - const sqlite = new Database(useRuntimeConfig().database); + const database = useRuntimeConfig().database; + const sqlite = new Database(database); instance = drizzle({ client: sqlite, schema }); instance.run("PRAGMA journal_mode = WAL;"); diff --git a/db.sqlite b/db.sqlite index de49dc7..658ba1b 100644 Binary files a/db.sqlite and b/db.sqlite differ diff --git a/db.sqlite-shm b/db.sqlite-shm index df4d197..e58aa6b 100644 Binary files a/db.sqlite-shm and b/db.sqlite-shm differ diff --git a/db.sqlite-wal b/db.sqlite-wal index 8502f26..e90d54e 100644 Binary files a/db.sqlite-wal and b/db.sqlite-wal differ diff --git a/db/schema.ts b/db/schema.ts index 0f72ba1..897541c 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -26,10 +26,10 @@ export const userSessionsTable = sqliteTable("user_sessions", { export const userPermissionsTable = sqliteTable("user_permissions", { id: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }), - permissions: text().notNull(), + permission: text().notNull(), }, (table): SQLiteTableExtraConfig => { return { - pk: primaryKey({ columns: [table.id, table.permissions] }), + pk: primaryKey({ columns: [table.id, table.permission] }), } }); @@ -43,16 +43,16 @@ export const explorerContentTable = sqliteTable("explorer_content", { private: int({ mode: 'boolean' }).default(false), }); -export const usersRelation = relations(usersTable, ({one, many}) => ({ +export const usersRelation = relations(usersTable, ({ one, many }) => ({ data: one(usersDataTable, { fields: [usersTable.id], references: [usersDataTable.id], }), session: many(userSessionsTable), permission: many(userPermissionsTable), content: many(explorerContentTable), })); -export const usersDataRelation = relations(usersDataTable, ({one}) => ({ +export const usersDataRelation = relations(usersDataTable, ({ one }) => ({ users: one(usersTable, { fields: [usersDataTable.id], references: [usersTable.id], }), })); -export const userSessionsRelation = relations(userSessionsTable, ({one}) => ({ +export const userSessionsRelation = relations(userSessionsTable, ({ one }) => ({ users: one(usersTable, { fields: [userSessionsTable.user_id], references: [usersTable.id], }), })); export const userPermissionsRelation = relations(userPermissionsTable, ({ one }) => ({ diff --git a/drizzle/0002_messy_solo.sql b/drizzle/0002_messy_solo.sql new file mode 100644 index 0000000..c6502ea --- /dev/null +++ b/drizzle/0002_messy_solo.sql @@ -0,0 +1,12 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_user_permissions` ( + `id` integer NOT NULL, + `permission` text NOT NULL, + PRIMARY KEY(`id`, `permission`), + FOREIGN KEY (`id`) REFERENCES `users`(`id`) ON UPDATE cascade ON DELETE cascade +); +--> statement-breakpoint +INSERT INTO `__new_user_permissions`("id", "permission") SELECT "id", "permission" FROM `user_permissions`;--> statement-breakpoint +DROP TABLE `user_permissions`;--> statement-breakpoint +ALTER TABLE `__new_user_permissions` RENAME TO `user_permissions`;--> statement-breakpoint +PRAGMA foreign_keys=ON; \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..dfe0c7c --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,300 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "6da7ff20-0db8-4055-a353-bb0ea2fa5e0b", + "prevId": "854cab71-b937-4f4f-80b0-cbb09c7b5944", + "tables": { + "explorer_content": { + "name": "explorer_content", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "navigable": { + "name": "navigable", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "private": { + "name": "private", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "explorer_content_owner_users_id_fk": { + "name": "explorer_content_owner_users_id_fk", + "tableFrom": "explorer_content", + "tableTo": "users", + "columnsFrom": [ + "owner" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_permissions": { + "name": "user_permissions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_permissions_id_users_id_fk": { + "name": "user_permissions_id_users_id_fk", + "tableFrom": "user_permissions", + "tableTo": "users", + "columnsFrom": [ + "id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_permissions_id_permission_pk": { + "columns": [ + "id", + "permission" + ], + "name": "user_permissions_id_permission_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_sessions": { + "name": "user_sessions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_sessions_user_id_users_id_fk": { + "name": "user_sessions_user_id_users_id_fk", + "tableFrom": "user_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_sessions_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "user_sessions_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users_data": { + "name": "users_data", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "signin": { + "name": "signin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "users_data_id_users_id_fk": { + "name": "users_data_id_users_id_fk", + "tableFrom": "users_data", + "tableTo": "users", + "columnsFrom": [ + "id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "users_hash_unique": { + "name": "users_hash_unique", + "columns": [ + "hash" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": { + "\"user_permissions\".\"permissions\"": "\"user_permissions\".\"permission\"" + } + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 9d07fdf..614b26a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1730832678255, "tag": "0001_sticky_jack_flag", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1730985155814, + "tag": "0002_messy_solo", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/relations.ts b/drizzle/relations.ts new file mode 100644 index 0000000..08e8ed3 --- /dev/null +++ b/drizzle/relations.ts @@ -0,0 +1,37 @@ +import { relations } from "drizzle-orm/relations"; +import { users, explorerContent, userSessions, usersData, userPermissions } from "./schema"; + +export const explorerContentRelations = relations(explorerContent, ({one}) => ({ + user: one(users, { + fields: [explorerContent.owner], + references: [users.id] + }), +})); + +export const usersRelations = relations(users, ({many}) => ({ + explorerContents: many(explorerContent), + userSessions: many(userSessions), + usersData: many(usersData), + userPermissions: many(userPermissions), +})); + +export const userSessionsRelations = relations(userSessions, ({one}) => ({ + user: one(users, { + fields: [userSessions.userId], + references: [users.id] + }), +})); + +export const usersDataRelations = relations(usersData, ({one}) => ({ + user: one(users, { + fields: [usersData.id], + references: [users.id] + }), +})); + +export const userPermissionsRelations = relations(userPermissions, ({one}) => ({ + user: one(users, { + fields: [userPermissions.id], + references: [users.id] + }), +})); \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts new file mode 100644 index 0000000..a9b4539 --- /dev/null +++ b/drizzle/schema.ts @@ -0,0 +1,57 @@ +import { sqliteTable, AnySQLiteColumn, foreignKey, text, integer, blob, primaryKey, uniqueIndex } from "drizzle-orm/sqlite-core" + import { sql } from "drizzle-orm" + +export const explorerContent = sqliteTable("explorer_content", { + path: text().primaryKey().notNull(), + owner: integer().notNull().references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" } ), + title: text().notNull(), + type: text().notNull(), + content: blob(), + navigable: integer().default(true), + private: integer().default(false), +}); + +export const userSessions = sqliteTable("user_sessions", { + id: integer().notNull(), + userId: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" } ), + timestamp: integer().notNull(), +}, +(table) => { + return { + pk0: primaryKey({ columns: [table.id, table.userId], name: "user_sessions_id_user_id_pk"}) + } +}); + +export const usersData = sqliteTable("users_data", { + id: integer().primaryKey().notNull().references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" } ), + signin: integer().notNull(), +}); + +export const users = sqliteTable("users", { + id: integer().primaryKey({ autoIncrement: true }).notNull(), + username: text().notNull(), + email: text().notNull(), + hash: text().notNull(), + state: integer().default(0).notNull(), +}, +(table) => { + return { + hashUnique: uniqueIndex("users_hash_unique").on(table.hash), + emailUnique: uniqueIndex("users_email_unique").on(table.email), + usernameUnique: uniqueIndex("users_username_unique").on(table.username), + } +}); + +export const userPermissions = sqliteTable("user_permissions", { + id: integer().notNull().references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" } ), + permissions: text().notNull(), +}, +(table) => { + return { + pk0: primaryKey({ columns: [table.id, table.permissions], name: "user_permissions_id_permissions_pk"}) + } +}); + +export const drizzleMigrations = sqliteTable("__drizzle_migrations", { +}); + diff --git a/middleware/auth.global.ts b/middleware/auth.global.ts index b302d00..6dab773 100644 --- a/middleware/auth.global.ts +++ b/middleware/auth.global.ts @@ -1,9 +1,8 @@ export default defineNuxtRouteMiddleware(async (to, from) => { - const { loggedIn, ready, fetch } = useUserSession(); + const { loggedIn, fetch, user } = useUserSession(); const meta = to.meta; - if(!ready) - await fetch(); + await fetch(); if(!!meta.guestsGoesTo && !loggedIn.value) { @@ -11,12 +10,40 @@ export default defineNuxtRouteMiddleware(async (to, from) => { } else if(meta.requireAuth && !loggedIn.value) { - return abortNavigation(); + return abortNavigation({ statusCode: 401, message: 'Unauthorized', }); } else if(!!meta.usersGoesTo && loggedIn.value) { return navigateTo(meta.usersGoesTo); } + else if(!!meta.validState && (!loggedIn.value || (user.value?.state ?? 0) === 0)) + { + return abortNavigation({ statusCode: 401, message: 'Unauthorized', }); + } + else if(!!meta.rights) + { + if(!user.value) + { + return abortNavigation({ statusCode: 401, message: 'Unauthorized', }); + } + else + { + let valid = false; + for(let i = 0; i < meta.rights.length; i++) + { + const list = meta.rights[i].split(' '); + + if(list.every(e => user.value.permissions.includes(e))) + { + valid = true; + break; + } + } + + if(!valid) + return abortNavigation({ statusCode: 401, message: 'Unauthorized', }); + } + } return; }); \ No newline at end of file diff --git a/package.json b/package.json index 837dd7b..aefe438 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@nuxtjs/tailwindcss": "^6.12.2", "@vueuse/nuxt": "^11.1.0", "drizzle-orm": "^0.35.3", - "nuxt": "^3.13.2", + "nuxt": "^3.14.159", "nuxt-security": "^2.0.0", "radix-vue": "^1.9.8", "vue": "latest", diff --git a/pages/admin/index.vue b/pages/admin/index.vue index e1f6e9f..9cf47b5 100644 --- a/pages/admin/index.vue +++ b/pages/admin/index.vue @@ -1,6 +1,10 @@ diff --git a/pages/user/profile.vue b/pages/user/profile.vue index ad58f34..03acf1d 100644 --- a/pages/user/profile.vue +++ b/pages/user/profile.vue @@ -9,8 +9,8 @@ let { user, clear } = useUserSession();