all in a days work

This commit is contained in:
Evert Prants 2024-05-16 23:17:06 +03:00
parent 64b9c16eed
commit 5e178a6a19
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
35 changed files with 3953 additions and 4 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
devdocker

16
drizzle.config.ts Normal file
View File

@ -0,0 +1,16 @@
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
dialect: 'mysql',
schema: './src/lib/server/drizzle/schema.ts',
out: './src/lib/server/drizzle/migrations',
dbCredentials: {
host: process.env.DATABASE_HOST as string,
port: Number(process.env.DATABASE_PORT) || 3306,
database: process.env.DATABASE_DB as string,
user: process.env.DATABASE_USER as string,
password: process.env.DATABASE_PASS as string
},
verbose: true,
strict: true
})

1564
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,9 +15,13 @@
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/bcryptjs": "^2.4.6",
"@types/eslint": "^8.56.0",
"@types/node": "^20.12.12",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"drizzle-kit": "^0.21.2",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
@ -31,6 +35,13 @@
},
"type": "module",
"dependencies": {
"@sveltejs/adapter-node": "^5.0.1"
"@sveltejs/adapter-node": "^5.0.1",
"bcryptjs": "^2.4.3",
"drizzle-orm": "^0.30.10",
"mysql2": "^3.9.7",
"otplib": "^12.0.1",
"svelte-kit-cookie-session": "^4.0.0",
"sveltekit-i18n": "^2.4.2",
"uuid": "^9.0.1"
}
}

78
src/app.css Normal file
View File

@ -0,0 +1,78 @@
*,
*::before,
*::after {
box-sizing: border-box;
}
:root {
--in-text-color: #fff;
--in-link-color: #fff;
--in-outline-color: #00aaff;
--in-normalized-background: #000;
--in-input-background: #fff;
--in-input-color: #000;
--in-input-border-color: #ddd;
--in-focus-outline: 3px solid var(--in-outline-color);
}
:root {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
color: var(--in-text-color);
}
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
body {
background-color: var(--in-normalized-background);
background-image: url('/background.jpg');
background-attachment: fixed;
background-repeat: no-repeat;
background-size: cover;
}
h1,
h2,
h3,
h4,
h5 {
margin-top: 0;
}
a {
color: var(--in-link-color);
&:visited {
color: var(--in-link-color);
}
&:focus-visible {
outline: var(--in-focus-outline);
}
}
a[target='_blank']::after {
content: '';
background-image: url('data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 style=%27width:24px;height:24px%27 viewBox=%270 0 24 24%27%3E%3Cpath fill=%27%23ffffff%27 d=%27M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z%27 /%3E%3C/svg%3E');
width: 0.95rem;
height: 0.95rem;
display: inline-block;
margin-left: 2px;
vertical-align: top;
}

18
src/app.d.ts vendored
View File

@ -1,10 +1,24 @@
import type { UserSession } from '$lib/server/users/types';
import type { Session } from 'svelte-kit-cookie-session';
type SessionData = {
user?: UserSession;
}
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
interface Locals {
session: Session<SessionData>;
}
interface PageData {
session: SessionData;
}
// interface PageState {}
// interface Platform {}
}

7
src/hooks.server.ts Normal file
View File

@ -0,0 +1,7 @@
import { SESSION_SECRET } from '$env/static/private';
import '$lib/server/drizzle';
import { handleSession } from 'svelte-kit-cookie-session';
export const handle = handleSession({
secret: SESSION_SECRET
})

View File

@ -0,0 +1,36 @@
<script lang="ts">
export let type: 'button' | 'submit' = 'button';
export let variant: 'default' | 'primary' | 'link' = 'default';
</script>
<button {type} class="btn btn-{variant}"><slot /></button>
<style>
.btn {
appearance: none;
border: 0;
padding: 0;
background: transparent;
color: var(--in-text-color);
font-size: 1rem;
cursor: pointer;
&:focus-visible {
outline: var(--in-focus-outline);
}
}
.btn-link {
text-decoration: underline;
}
.btn-default,
.btn-primary {
background-color: #fff;
color: #000;
border: 2px solid #ddd;
padding: 6px 12px;
border-radius: 4px;
font-weight: 700;
}
</style>

View File

@ -0,0 +1,3 @@
<form action="/account?/logout" method="POST">
<button type="submit" class="btn btn-link">Log out</button>
</form>

View File

@ -0,0 +1,33 @@
<div class="form-control">
<slot></slot>
</div>
<style>
:global(input) {
&:not([type]),
&[type=text],
&[type=password],
&[type=email] {
padding: 8px;
font-size: 1rem;
background-color: var(--in-input-background);
color: var(--in-input-color);
border: 2px solid var(--in-input-border-color);
border-radius: 6px;
&:focus-visible {
outline: var(--in-focus-outline);
}
}
}
:global(label) {
margin-bottom: 2px;
}
.form-control {
display: flex;
flex-direction: column;
}
</style>

View File

@ -0,0 +1,4 @@
<div class="form-section">
<slot />
</div>

View File

@ -0,0 +1,10 @@
<div class="form-wrapper">
<slot />
</div>
<style>
:global(.form-control) {
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,28 @@
{
"username": "Username",
"displayName": "Display Name",
"changeEmail": "Change email address",
"currentEmail": "Current email address",
"newEmail": "New email address",
"changePassword": "Change password",
"currentPassword": "Current password",
"newPassword": "New password",
"repeatPassword": "Repeat new password",
"submit": "Submit",
"login": {
"title": "Log in",
"email": "Email",
"password": "Password",
"submit": "Log in"
},
"errors": {
"invalidLogin": "Invalid email or password!",
"invalidRequest": "Invalid request! Please try again.",
"emailRequired": "Email address is required.",
"invalidEmail": "The email address is invalid.",
"passwordRequired": "The password is required.",
"passwordMismatch": "The passwords do not match!",
"invalidPassword": "The provided password is invalid.",
"invalidDisplayName": "The provided display name is invalid."
}
}

View File

@ -0,0 +1,5 @@
{
"siteName": "Icy Network",
"description": "Icy Network is a Single-Sign-On service used by other applications.",
"cookieDisclaimer": "The website may use temporary cookies for storing your login session and ensuring your security. This web service is&nbsp;<a href=\"https://git.icynet.eu/IcyNetwork/icynet-auth-server\" target=\"_blank\">completely open source</a> and can be audited by anyone."
}

18
src/lib/i18n/index.ts Normal file
View File

@ -0,0 +1,18 @@
import i18n from 'sveltekit-i18n';
const config = {
loaders: [
{
locale: 'en',
key: 'common',
loader: async () => await import('./en/common.json')
},
{
locale: 'en',
key: 'account',
loader: async () => await import('./en/account.json')
}
]
};
export const { t, locale, locales, loading, loadTranslations } = new i18n(config);

View File

@ -0,0 +1,79 @@
import { Changesets } from './changesets';
import { CryptoUtils } from './crypto-utils';
import type { User } from './drizzle';
import { TimeOTP } from './users/totp';
export interface ChallengeBody<T> {
aud: string;
data: T;
}
export class Challenge {
static async issueChallenge<TChallenge>(
challenge: TChallenge,
recipient: string
): Promise<string> {
const body = <ChallengeBody<TChallenge>>{
aud: recipient,
data: challenge
};
return CryptoUtils.encryptChallenge(body);
}
static async verifyChallenge<TRes>(
challenge: string,
recipient: string,
code: string,
secret: string
) {
const { aud, data }: ChallengeBody<TRes> = await CryptoUtils.decryptChallenge(challenge);
if (aud !== recipient) {
throw new Error('Invalid challenge');
}
if (!TimeOTP.validate(secret, code)) {
throw new Error('Invalid token');
}
return data;
}
static async challengeFromBody<TRes>(
body: FormData,
recipient: string,
secret: string
): Promise<TRes | undefined> {
const { challenge, otpCode } = Changesets.take<{ challenge?: string; otpCode?: string }>(
['challenge', 'otpCode'],
body
);
if (!challenge || !otpCode) {
return;
}
return Challenge.verifyChallenge<TRes>(challenge, recipient, otpCode, secret);
}
static async authorizedChanges<TRes>(
fields: (keyof TRes)[],
body: FormData,
subject: User
): Promise<Partial<TRes>> {
if (!body.has('challenge')) {
return Changesets.take<TRes>(fields, body);
}
const userOtp = await TimeOTP.getUserOtp(subject);
if (!userOtp) {
throw new Error('Invalid request');
}
const data = await Challenge.challengeFromBody<TRes>(body, subject.uuid, userOtp.token);
if (!data) {
throw new Error('Invalid request');
}
return data;
}
}

View File

@ -0,0 +1,14 @@
export class Changesets {
static take<TRes>(
fields: (keyof TRes)[],
body: FormData,
challenge?: Partial<TRes>
): Partial<TRes> {
return fields.reduce<Partial<TRes>>((accum, field) => {
accum[field] = challenge
? challenge[field]
: ((body.get(field as string) as string)?.trim() as TRes[typeof field]);
return accum;
}, {});
}
}

View File

@ -0,0 +1,63 @@
import { CHALLENGE_SECRET } from '$env/static/private';
import * as crypto from 'crypto';
import { v4 } from 'uuid';
const IV_LENGTH = 16;
const ALGORITHM = 'aes-256-cbc';
export class CryptoUtils {
public static generateString(length: number): string {
return crypto.randomBytes(length).toString('hex').slice(0, length);
}
public static generateSecret(): string {
return crypto.randomBytes(256 / 8).toString('hex');
}
public static insecureHash(input: string): string {
return crypto.createHash('md5').update(input).digest('hex');
}
public static createUUID(): string {
return v4();
}
// https://stackoverflow.com/q/52212430
/**
* Symmetric encryption function
* @param text String to encrypt
* @param key Encryption key
* @returns Encrypted text
*/
public static encrypt(text: string, key: string): string {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key, 'hex'), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return `${iv.toString('hex')}:${encrypted.toString('hex')}`;
}
/**
* Symmetric decryption function
* @param text Encrypted string
* @param key Decryption key
* @returns Decrypted text
*/
public static decrypt(text: string, key: string): string {
const [iv, encryptedText] = text.split(':').map((part) => Buffer.from(part, 'hex'));
const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key, 'hex'), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
public static async encryptChallenge<T>(challenge: T): Promise<string> {
return this.encrypt(JSON.stringify(challenge), CHALLENGE_SECRET);
}
public static async decryptChallenge<T>(challenge: string): Promise<T> {
return JSON.parse(this.decrypt(challenge, CHALLENGE_SECRET));
}
}

View File

@ -0,0 +1,14 @@
import { DATABASE_DB, DATABASE_HOST, DATABASE_PASS } from '$env/static/private';
import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import * as schema from './schema';
const connection = await mysql.createConnection({
host: DATABASE_HOST,
user: DATABASE_PASS,
password: DATABASE_PASS,
database: DATABASE_DB
});
export const db = drizzle(connection, { schema, mode: 'default' });
export * from './schema';

View File

@ -0,0 +1,137 @@
-- Current sql file was generated after introspecting the database
-- If you want to run this migration please uncomment this code before executing migrations
/*
CREATE TABLE `audit_log` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`action` text NOT NULL,
`content` text DEFAULT 'NULL',
`actor_ip` text DEFAULT 'NULL',
`actor_ua` text DEFAULT 'NULL',
`flagged` tinyint NOT NULL DEFAULT 0,
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
`actorId` int(11) DEFAULT 'NULL'
);
--> statement-breakpoint
CREATE TABLE `document` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`title` text NOT NULL,
`slug` text NOT NULL,
`body` text NOT NULL,
`authorId` int(11) DEFAULT 'NULL',
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
);
--> statement-breakpoint
CREATE TABLE `o_auth2_client` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`client_id` varchar(36) NOT NULL,
`client_secret` text NOT NULL,
`title` varchar(255) NOT NULL,
`description` text DEFAULT 'NULL',
`scope` text DEFAULT 'NULL',
`grants` text NOT NULL DEFAULT ''authorization_code'',
`activated` tinyint NOT NULL DEFAULT 0,
`verified` tinyint NOT NULL DEFAULT 0,
`pictureId` int(11) DEFAULT 'NULL',
`ownerId` int(11) DEFAULT 'NULL',
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
CONSTRAINT `IDX_e9d16c213910ad57bd05e97b42` UNIQUE(`client_id`)
);
--> statement-breakpoint
CREATE TABLE `o_auth2_client_authorization` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`scope` text DEFAULT 'NULL',
`expires_at` timestamp NOT NULL DEFAULT 'current_timestamp()',
`clientId` int(11) DEFAULT 'NULL',
`userId` int(11) DEFAULT 'NULL',
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
);
--> statement-breakpoint
CREATE TABLE `o_auth2_client_url` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`url` varchar(255) NOT NULL,
`type` enum('redirect_uri','terms','privacy','website') NOT NULL,
`created_at` timestamp(6) NOT NULL DEFAULT 'current_timestamp(6)',
`updated_at` timestamp(6) NOT NULL DEFAULT 'current_timestamp(6)',
`clientId` int(11) DEFAULT 'NULL'
);
--> statement-breakpoint
CREATE TABLE `o_auth2_token` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`type` enum('code','access_token','refresh_token') NOT NULL,
`token` text NOT NULL,
`scope` text DEFAULT 'NULL',
`expires_at` timestamp NOT NULL DEFAULT 'current_timestamp()',
`userId` int(11) DEFAULT 'NULL',
`clientId` int(11) DEFAULT 'NULL',
`nonce` text DEFAULT 'NULL',
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
`pcke` text DEFAULT 'NULL'
);
--> statement-breakpoint
CREATE TABLE `privilege` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`name` text NOT NULL
);
--> statement-breakpoint
CREATE TABLE `upload` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`original_name` varchar(255) NOT NULL,
`mimetype` varchar(255) NOT NULL,
`file` varchar(255) NOT NULL,
`uploaderId` int(11) DEFAULT 'NULL',
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
);
--> statement-breakpoint
CREATE TABLE `user` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`uuid` varchar(36) NOT NULL,
`username` varchar(26) NOT NULL,
`email` varchar(255) NOT NULL,
`display_name` varchar(32) NOT NULL,
`password` text DEFAULT 'NULL',
`activated` tinyint NOT NULL DEFAULT 0,
`activity_at` timestamp NOT NULL DEFAULT 'current_timestamp()',
`pictureId` int(11) DEFAULT 'NULL',
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
CONSTRAINT `IDX_a95e949168be7b7ece1a2382fe` UNIQUE(`uuid`),
CONSTRAINT `IDX_78a916df40e02a9deb1c4b75ed` UNIQUE(`username`),
CONSTRAINT `IDX_e12875dfb3b1d92d7d7c5377e2` UNIQUE(`email`)
);
--> statement-breakpoint
CREATE TABLE `user_privileges_privilege` (
`userId` int(11) NOT NULL,
`privilegeId` int(11) NOT NULL
);
--> statement-breakpoint
CREATE TABLE `user_token` (
`id` int(11) AUTO_INCREMENT NOT NULL,
`token` text NOT NULL,
`type` enum('generic','activation','deactivation','password','login','gdpr','totp','public_key','recovery') NOT NULL,
`expires_at` timestamp DEFAULT 'NULL',
`userId` int(11) DEFAULT 'NULL',
`nonce` text DEFAULT 'NULL',
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
);
--> statement-breakpoint
ALTER TABLE `audit_log` ADD CONSTRAINT `FK_cb6aa6f6fd56f08eafb60316225` FOREIGN KEY (`actorId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `document` ADD CONSTRAINT `FK_6a2eb13cadfc503989cbe367572` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `o_auth2_client` ADD CONSTRAINT `FK_4a6c878506b872e85b3d07f6252` FOREIGN KEY (`ownerId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `o_auth2_client` ADD CONSTRAINT `FK_e8d65b1eec13474e493420517d7` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `FK_8227110f58510b7233f3db90cfb` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `FK_9ca9ebb654e7ce71954d5fdb281` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `o_auth2_client_url` ADD CONSTRAINT `FK_aca59c7bdd65987487eea98d00f` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `o_auth2_token` ADD CONSTRAINT `FK_3ecb760b321ef9bbab635f05b45` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `o_auth2_token` ADD CONSTRAINT `FK_81ffb9b8d672cf3af1af9e789f3` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `upload` ADD CONSTRAINT `FK_7b8d52838a953b188255682597b` FOREIGN KEY (`uploaderId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE `user` ADD CONSTRAINT `FK_7478a15985dbfa32ed5fc77a7a1` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `FK_0664a7ff494a1859a09014c0f17` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `FK_e71171f4ed20bc8564a1819d0b7` FOREIGN KEY (`privilegeId`) REFERENCES `privilege`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE `user_token` ADD CONSTRAINT `FK_d37db50eecdf9b8ce4eedd2f918` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `IDX_0664a7ff494a1859a09014c0f1` ON `user_privileges_privilege` (`userId`);--> statement-breakpoint
CREATE INDEX `IDX_e71171f4ed20bc8564a1819d0b` ON `user_privileges_privilege` (`privilegeId`);
*/

View File

@ -0,0 +1,985 @@
{
"id": "00000000-0000-0000-0000-000000000000",
"prevId": "",
"version": "5",
"dialect": "mysql",
"tables": {
"audit_log": {
"name": "audit_log",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"action": {
"autoincrement": false,
"name": "action",
"type": "text",
"primaryKey": false,
"notNull": true
},
"content": {
"default": "'NULL'",
"autoincrement": false,
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": false
},
"actor_ip": {
"default": "'NULL'",
"autoincrement": false,
"name": "actor_ip",
"type": "text",
"primaryKey": false,
"notNull": false
},
"actor_ua": {
"default": "'NULL'",
"autoincrement": false,
"name": "actor_ua",
"type": "text",
"primaryKey": false,
"notNull": false
},
"flagged": {
"default": 0,
"autoincrement": false,
"name": "flagged",
"type": "tinyint",
"primaryKey": false,
"notNull": true
},
"created_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "created_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
},
"actorId": {
"default": "'NULL'",
"autoincrement": false,
"name": "actorId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"FK_cb6aa6f6fd56f08eafb60316225": {
"name": "FK_cb6aa6f6fd56f08eafb60316225",
"tableFrom": "audit_log",
"tableTo": "user",
"columnsFrom": [
"actorId"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"uniqueConstraints": {}
},
"document": {
"name": "document",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"title": {
"autoincrement": false,
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true
},
"slug": {
"autoincrement": false,
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": true
},
"body": {
"autoincrement": false,
"name": "body",
"type": "text",
"primaryKey": false,
"notNull": true
},
"authorId": {
"default": "'NULL'",
"autoincrement": false,
"name": "authorId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "created_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "updated_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"FK_6a2eb13cadfc503989cbe367572": {
"name": "FK_6a2eb13cadfc503989cbe367572",
"tableFrom": "document",
"tableTo": "user",
"columnsFrom": [
"authorId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"uniqueConstraints": {}
},
"migrations": {
"name": "migrations",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"timestamp": {
"autoincrement": false,
"name": "timestamp",
"type": "bigint(20)",
"primaryKey": false,
"notNull": true
},
"name": {
"autoincrement": false,
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {},
"uniqueConstraints": {}
},
"o_auth2_client": {
"name": "o_auth2_client",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"client_id": {
"autoincrement": false,
"name": "client_id",
"type": "varchar(36)",
"primaryKey": false,
"notNull": true
},
"client_secret": {
"autoincrement": false,
"name": "client_secret",
"type": "text",
"primaryKey": false,
"notNull": true
},
"title": {
"autoincrement": false,
"name": "title",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"description": {
"default": "'NULL'",
"autoincrement": false,
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"scope": {
"default": "'NULL'",
"autoincrement": false,
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"grants": {
"default": "''authorization_code''",
"autoincrement": false,
"name": "grants",
"type": "text",
"primaryKey": false,
"notNull": true
},
"activated": {
"default": 0,
"autoincrement": false,
"name": "activated",
"type": "tinyint",
"primaryKey": false,
"notNull": true
},
"verified": {
"default": 0,
"autoincrement": false,
"name": "verified",
"type": "tinyint",
"primaryKey": false,
"notNull": true
},
"pictureId": {
"default": "'NULL'",
"autoincrement": false,
"name": "pictureId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
},
"ownerId": {
"default": "'NULL'",
"autoincrement": false,
"name": "ownerId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "created_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "updated_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"FK_4a6c878506b872e85b3d07f6252": {
"name": "FK_4a6c878506b872e85b3d07f6252",
"tableFrom": "o_auth2_client",
"tableTo": "user",
"columnsFrom": [
"ownerId"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"FK_e8d65b1eec13474e493420517d7": {
"name": "FK_e8d65b1eec13474e493420517d7",
"tableFrom": "o_auth2_client",
"tableTo": "upload",
"columnsFrom": [
"pictureId"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"uniqueConstraints": {
"IDX_e9d16c213910ad57bd05e97b42": {
"name": "IDX_e9d16c213910ad57bd05e97b42",
"columns": [
"client_id"
]
}
}
},
"o_auth2_client_authorization": {
"name": "o_auth2_client_authorization",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"scope": {
"default": "'NULL'",
"autoincrement": false,
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"default": "'current_timestamp()'",
"autoincrement": false,
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"clientId": {
"default": "'NULL'",
"autoincrement": false,
"name": "clientId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
},
"userId": {
"default": "'NULL'",
"autoincrement": false,
"name": "userId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "created_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"FK_8227110f58510b7233f3db90cfb": {
"name": "FK_8227110f58510b7233f3db90cfb",
"tableFrom": "o_auth2_client_authorization",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"FK_9ca9ebb654e7ce71954d5fdb281": {
"name": "FK_9ca9ebb654e7ce71954d5fdb281",
"tableFrom": "o_auth2_client_authorization",
"tableTo": "o_auth2_client",
"columnsFrom": [
"clientId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"uniqueConstraints": {}
},
"o_auth2_client_url": {
"name": "o_auth2_client_url",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"url": {
"autoincrement": false,
"name": "url",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"type": {
"autoincrement": false,
"name": "type",
"type": "enum('redirect_uri','terms','privacy','website')",
"primaryKey": false,
"notNull": true
},
"created_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "created_at",
"type": "timestamp(6)",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "updated_at",
"type": "timestamp(6)",
"primaryKey": false,
"notNull": true
},
"clientId": {
"default": "'NULL'",
"autoincrement": false,
"name": "clientId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"FK_aca59c7bdd65987487eea98d00f": {
"name": "FK_aca59c7bdd65987487eea98d00f",
"tableFrom": "o_auth2_client_url",
"tableTo": "o_auth2_client",
"columnsFrom": [
"clientId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"uniqueConstraints": {}
},
"o_auth2_token": {
"name": "o_auth2_token",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"type": {
"autoincrement": false,
"name": "type",
"type": "enum('code','access_token','refresh_token')",
"primaryKey": false,
"notNull": true
},
"token": {
"autoincrement": false,
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"scope": {
"default": "'NULL'",
"autoincrement": false,
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"default": "'current_timestamp()'",
"autoincrement": false,
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"userId": {
"default": "'NULL'",
"autoincrement": false,
"name": "userId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
},
"clientId": {
"default": "'NULL'",
"autoincrement": false,
"name": "clientId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
},
"nonce": {
"default": "'NULL'",
"autoincrement": false,
"name": "nonce",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "created_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "updated_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
},
"pcke": {
"default": "'NULL'",
"autoincrement": false,
"name": "pcke",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"FK_3ecb760b321ef9bbab635f05b45": {
"name": "FK_3ecb760b321ef9bbab635f05b45",
"tableFrom": "o_auth2_token",
"tableTo": "o_auth2_client",
"columnsFrom": [
"clientId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"FK_81ffb9b8d672cf3af1af9e789f3": {
"name": "FK_81ffb9b8d672cf3af1af9e789f3",
"tableFrom": "o_auth2_token",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"uniqueConstraints": {}
},
"privilege": {
"name": "privilege",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"name": {
"autoincrement": false,
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {},
"uniqueConstraints": {}
},
"upload": {
"name": "upload",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"original_name": {
"autoincrement": false,
"name": "original_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"mimetype": {
"autoincrement": false,
"name": "mimetype",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"file": {
"autoincrement": false,
"name": "file",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"uploaderId": {
"default": "'NULL'",
"autoincrement": false,
"name": "uploaderId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "created_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "updated_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"FK_7b8d52838a953b188255682597b": {
"name": "FK_7b8d52838a953b188255682597b",
"tableFrom": "upload",
"tableTo": "user",
"columnsFrom": [
"uploaderId"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "cascade"
}
},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"autoincrement": true,
"name": "id",
"type": "int(11)",
"primaryKey": false,
"notNull": true
},
"uuid": {
"autoincrement": false,
"name": "uuid",
"type": "varchar(36)",
"primaryKey": false,
"notNull": true
},
"username": {
"autoincrement": false,
"name": "username",
"type": "varchar(26)",
"primaryKey": false,
"notNull": true
},
"email": {
"autoincrement": false,
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"display_name": {
"autoincrement": false,
"name": "display_name",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"password": {
"default": "'NULL'",
"autoincrement": false,
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"activated": {
"default": 0,
"autoincrement": false,
"name": "activated",
"type": "tinyint",
"primaryKey": false,
"notNull": true
},
"activity_at": {
"default": "'current_timestamp()'",
"autoincrement": false,
"name": "activity_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"pictureId": {
"default": "'NULL'",
"autoincrement": false,
"name": "pictureId",
"type": "int(11)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "created_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"default": "'current_timestamp(6)'",
"autoincrement": false,
"name": "updated_at",
"type": "datetime(6)",
"primaryKey": false,
"notNull": true
}
},
"compositePrimaryKeys": {},
"indexes": {},
"foreignKeys": {
"FK_7478a15985dbfa32ed5fc77a7a1": {
"name": "FK_7478a15985dbfa32ed5fc77a7a1",