Merge branch 'dev' into add-file-category

This commit is contained in:
static
2025-01-21 09:37:59 +09:00
40 changed files with 1881 additions and 3102 deletions

View File

@@ -1,61 +1,32 @@
import {
sqliteTable,
text,
integer,
primaryKey,
foreignKey,
unique,
} from "drizzle-orm/sqlite-core";
import { user } from "./user";
import type { ColumnType, Generated } from "kysely";
export const client = sqliteTable(
"client",
{
id: integer("id").primaryKey({ autoIncrement: true }),
encPubKey: text("encryption_public_key").notNull().unique(), // Base64
sigPubKey: text("signature_public_key").notNull().unique(), // Base64
},
(t) => ({
unq: unique().on(t.encPubKey, t.sigPubKey),
}),
);
interface ClientTable {
id: Generated<number>;
encryption_public_key: string; // Base64
signature_public_key: string; // Base64
}
export const userClient = sqliteTable(
"user_client",
{
userId: integer("user_id")
.notNull()
.references(() => user.id),
clientId: integer("client_id")
.notNull()
.references(() => client.id),
state: text("state", { enum: ["challenging", "pending", "active"] })
.notNull()
.default("challenging"),
},
(t) => ({
pk: primaryKey({ columns: [t.userId, t.clientId] }),
}),
);
export type UserClientState = "challenging" | "pending" | "active";
export const userClientChallenge = sqliteTable(
"user_client_challenge",
{
id: integer("id").primaryKey(),
userId: integer("user_id")
.notNull()
.references(() => user.id),
clientId: integer("client_id")
.notNull()
.references(() => client.id),
answer: text("answer").notNull().unique(), // Base64
allowedIp: text("allowed_ip").notNull(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
},
(t) => ({
ref: foreignKey({
columns: [t.userId, t.clientId],
foreignColumns: [userClient.userId, userClient.clientId],
}),
}),
);
interface UserClientTable {
user_id: number;
client_id: number;
state: ColumnType<UserClientState, UserClientState | undefined>;
}
interface UserClientChallengeTable {
id: Generated<number>;
user_id: number;
client_id: number;
answer: string; // Base64
allowed_ip: string;
expires_at: ColumnType<Date, Date, never>;
}
declare module "./index" {
interface Database {
client: ClientTable;
user_client: UserClientTable;
user_client_challenge: UserClientChallengeTable;
}
}

View File

@@ -1,95 +1,53 @@
import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core";
import { category } from "./category";
import { hsk } from "./hsk";
import { mek } from "./mek";
import { user } from "./user";
import type { ColumnType, Generated } from "kysely";
const ciphertext = (name: string) =>
text(name, { mode: "json" }).$type<{
ciphertext: string; // Base64
iv: string; // Base64
}>();
export type Ciphertext = {
ciphertext: string; // Base64
iv: string; // Base64
};
export const directory = sqliteTable(
"directory",
{
id: integer("id").primaryKey({ autoIncrement: true }),
parentId: integer("parent_id"),
userId: integer("user_id")
.notNull()
.references(() => user.id),
mekVersion: integer("master_encryption_key_version").notNull(),
encDek: text("encrypted_data_encryption_key").notNull().unique(), // Base64
dekVersion: integer("data_encryption_key_version", { mode: "timestamp_ms" }).notNull(),
encName: ciphertext("encrypted_name").notNull(),
},
(t) => ({
ref1: foreignKey({
columns: [t.parentId],
foreignColumns: [t.id],
}),
ref2: foreignKey({
columns: [t.userId, t.mekVersion],
foreignColumns: [mek.userId, mek.version],
}),
}),
);
interface DirectoryTable {
id: Generated<number>;
parent_id: number | null;
user_id: number;
master_encryption_key_version: number;
encrypted_data_encryption_key: string; // Base64
data_encryption_key_version: Date;
encrypted_name: Ciphertext;
}
export const directoryLog = sqliteTable("directory_log", {
id: integer("id").primaryKey({ autoIncrement: true }),
directoryId: integer("directory_id")
.notNull()
.references(() => directory.id, { onDelete: "cascade" }),
timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(),
action: text("action", { enum: ["create", "rename"] }).notNull(),
newName: ciphertext("new_name"),
});
interface DirectoryLogTable {
id: Generated<number>;
directory_id: number;
timestamp: ColumnType<Date, Date, never>;
action: "create" | "rename";
new_name: Ciphertext | null;
}
export const file = sqliteTable(
"file",
{
id: integer("id").primaryKey({ autoIncrement: true }),
parentId: integer("parent_id").references(() => directory.id),
userId: integer("user_id")
.notNull()
.references(() => user.id),
path: text("path").notNull().unique(),
mekVersion: integer("master_encryption_key_version").notNull(),
encDek: text("encrypted_data_encryption_key").notNull().unique(), // Base64
dekVersion: integer("data_encryption_key_version", { mode: "timestamp_ms" }).notNull(),
hskVersion: integer("hmac_secret_key_version"),
contentHmac: text("content_hmac"), // Base64
contentType: text("content_type").notNull(),
encContentIv: text("encrypted_content_iv").notNull(), // Base64
encContentHash: text("encrypted_content_hash").notNull(), // Base64
encName: ciphertext("encrypted_name").notNull(),
encCreatedAt: ciphertext("encrypted_created_at"),
encLastModifiedAt: ciphertext("encrypted_last_modified_at").notNull(),
},
(t) => ({
ref1: foreignKey({
columns: [t.userId, t.mekVersion],
foreignColumns: [mek.userId, mek.version],
}),
ref2: foreignKey({
columns: [t.userId, t.hskVersion],
foreignColumns: [hsk.userId, hsk.version],
}),
}),
);
interface FileTable {
id: Generated<number>;
parent_id: number | null;
user_id: number;
path: string;
master_encryption_key_version: number;
encrypted_data_encryption_key: string; // Base64
data_encryption_key_version: Date;
hmac_secret_key_version: number | null;
content_hmac: string | null; // Base64
content_type: string;
encrypted_content_iv: string; // Base64
encrypted_content_hash: string; // Base64
encrypted_name: Ciphertext;
encrypted_created_at: Ciphertext | null;
encrypted_last_modified_at: Ciphertext;
}
export const fileLog = sqliteTable("file_log", {
id: integer("id").primaryKey({ autoIncrement: true }),
fileId: integer("file_id")
.notNull()
.references(() => file.id, { onDelete: "cascade" }),
timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(),
action: text("action", {
enum: ["create", "rename", "addToCategory", "removeFromCategory"],
}).notNull(),
newName: ciphertext("new_name"),
categoryId: integer("category_id").references(() => category.id, { onDelete: "set null" }),
});
interface FileLogTable {
id: Generated<number>;
file_id: number;
timestamp: ColumnType<Date, Date, never>;
action: "create" | "rename";
new_name: Ciphertext | null;
}
export const fileCategory = sqliteTable(
"file_category",
@@ -107,3 +65,12 @@ export const fileCategory = sqliteTable(
}),
}),
);
declare module "./index" {
interface Database {
directory: DirectoryTable;
directory_log: DirectoryLogTable;
file: FileTable;
file_log: FileLogTable;
}
}

View File

@@ -1,44 +1,27 @@
import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core";
import { client } from "./client";
import { mek } from "./mek";
import { user } from "./user";
import type { ColumnType, Generated } from "kysely";
export const hsk = sqliteTable(
"hmac_secret_key",
{
userId: integer("user_id")
.notNull()
.references(() => user.id),
version: integer("version").notNull(),
state: text("state", { enum: ["active"] }).notNull(),
mekVersion: integer("master_encryption_key_version").notNull(),
encHsk: text("encrypted_key").notNull().unique(), // Base64
},
(t) => ({
pk: primaryKey({ columns: [t.userId, t.version] }),
ref: foreignKey({
columns: [t.userId, t.mekVersion],
foreignColumns: [mek.userId, mek.version],
}),
}),
);
export type HskState = "active";
export const hskLog = sqliteTable(
"hmac_secret_key_log",
{
id: integer("id").primaryKey({ autoIncrement: true }),
userId: integer("user_id")
.notNull()
.references(() => user.id),
hskVersion: integer("hmac_secret_key_version").notNull(),
timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(),
action: text("action", { enum: ["create"] }).notNull(),
actionBy: integer("action_by").references(() => client.id),
},
(t) => ({
ref: foreignKey({
columns: [t.userId, t.hskVersion],
foreignColumns: [hsk.userId, hsk.version],
}),
}),
);
interface HskTable {
user_id: number;
version: number;
state: HskState;
master_encryption_key_version: number;
encrypted_key: string; // Base64
}
interface HskLogTable {
id: Generated<number>;
user_id: number;
hmac_secret_key_version: number;
timestamp: ColumnType<Date, Date, never>;
action: "create";
action_by: number | null;
}
declare module "./index" {
interface Database {
hmac_secret_key: HskTable;
hmac_secret_key_log: HskLogTable;
}
}

View File

@@ -5,3 +5,6 @@ export * from "./hsk";
export * from "./mek";
export * from "./session";
export * from "./user";
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface Database {}

View File

@@ -1,60 +1,34 @@
import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core";
import { client } from "./client";
import { user } from "./user";
import type { ColumnType, Generated } from "kysely";
export const mek = sqliteTable(
"master_encryption_key",
{
userId: integer("user_id")
.notNull()
.references(() => user.id),
version: integer("version").notNull(),
state: text("state", { enum: ["active", "retired", "dead"] }).notNull(),
retiredAt: integer("retired_at", { mode: "timestamp_ms" }),
},
(t) => ({
pk: primaryKey({ columns: [t.userId, t.version] }),
}),
);
export type MekState = "active" | "retired" | "dead";
export const mekLog = sqliteTable(
"master_encryption_key_log",
{
id: integer("id").primaryKey({ autoIncrement: true }),
userId: integer("user_id")
.notNull()
.references(() => user.id),
mekVersion: integer("master_encryption_key_version").notNull(),
timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(),
action: text("action", { enum: ["create"] }).notNull(),
actionBy: integer("action_by").references(() => client.id),
},
(t) => ({
ref: foreignKey({
columns: [t.userId, t.mekVersion],
foreignColumns: [mek.userId, mek.version],
}),
}),
);
interface MekTable {
user_id: number;
version: number;
state: MekState;
}
export const clientMek = sqliteTable(
"client_master_encryption_key",
{
userId: integer("user_id")
.notNull()
.references(() => user.id),
clientId: integer("client_id")
.notNull()
.references(() => client.id),
mekVersion: integer("version").notNull(),
encMek: text("encrypted_key").notNull(), // Base64
encMekSig: text("encrypted_key_signature").notNull(), // Base64
},
(t) => ({
pk: primaryKey({ columns: [t.userId, t.clientId, t.mekVersion] }),
ref: foreignKey({
columns: [t.userId, t.mekVersion],
foreignColumns: [mek.userId, mek.version],
}),
}),
);
interface MekLogTable {
id: Generated<number>;
user_id: number;
master_encryption_key_version: number;
timestamp: ColumnType<Date, Date, never>;
action: "create";
action_by: number | null;
}
interface ClientMekTable {
user_id: number;
client_id: number;
version: number;
encrypted_key: string; // Base64
encrypted_key_signature: string; // Base64
}
declare module "./index" {
interface Database {
master_encryption_key: MekTable;
master_encryption_key_log: MekLogTable;
client_master_encryption_key: ClientMekTable;
}
}

View File

@@ -1,35 +1,27 @@
import { sqliteTable, text, integer, unique } from "drizzle-orm/sqlite-core";
import { client } from "./client";
import { user } from "./user";
import type { ColumnType, Generated } from "kysely";
export const session = sqliteTable(
"session",
{
id: text("id").notNull().primaryKey(),
userId: integer("user_id")
.notNull()
.references(() => user.id),
clientId: integer("client_id").references(() => client.id),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
lastUsedAt: integer("last_used_at", { mode: "timestamp_ms" }).notNull(),
lastUsedByIp: text("last_used_by_ip"),
lastUsedByUserAgent: text("last_used_by_user_agent"),
},
(t) => ({
unq: unique().on(t.userId, t.clientId),
}),
);
interface SessionTable {
id: string;
user_id: number;
client_id: number | null;
created_at: ColumnType<Date, Date, never>;
last_used_at: Date;
last_used_by_ip: string | null;
last_used_by_agent: string | null;
}
export const sessionUpgradeChallenge = sqliteTable("session_upgrade_challenge", {
id: integer("id").primaryKey(),
sessionId: text("session_id")
.notNull()
.references(() => session.id)
.unique(),
clientId: integer("client_id")
.notNull()
.references(() => client.id),
answer: text("answer").notNull().unique(), // Base64
allowedIp: text("allowed_ip").notNull(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
});
interface SessionUpgradeChallengeTable {
id: Generated<number>;
session_id: string;
client_id: number;
answer: string; // Base64
allowed_ip: string;
expires_at: ColumnType<Date, Date, never>;
}
declare module "./index" {
interface Database {
session: SessionTable;
session_upgrade_challenge: SessionUpgradeChallengeTable;
}
}

View File

@@ -1,8 +1,14 @@
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import type { Generated } from "kysely";
export const user = sqliteTable("user", {
id: integer("id").primaryKey({ autoIncrement: true }),
email: text("email").notNull().unique(),
password: text("password").notNull(),
nickname: text("nickname").notNull(),
});
interface UserTable {
id: Generated<number>;
email: string;
nickname: string;
password: string;
}
declare module "./index" {
interface Database {
user: UserTable;
}
}