mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-14 22:08:45 +00:00
프론트엔드에서 세션 ID 기반 인증 대응 및 DB 마이그레이션 스크립트 재생성
This commit is contained in:
@@ -30,12 +30,11 @@ docker compose up --build -d
|
|||||||
필수 환경 변수가 아닌 경우, 설정해야 하는 특별한 이유가 없다면 기본값을 사용하는 것이 좋아요.
|
필수 환경 변수가 아닌 경우, 설정해야 하는 특별한 이유가 없다면 기본값을 사용하는 것이 좋아요.
|
||||||
|
|
||||||
|이름|필수|기본값|설명|
|
|이름|필수|기본값|설명|
|
||||||
|-:|:-:|:-:|:-|
|
|:-|:-:|:-:|:-|
|
||||||
|`JWT_SECRET`|Y||JWT의 서명을 위해 사용돼요. 안전한 값으로 설정해 주세요.|
|
|`SESSION_SECRET`|Y||Session ID의 서명을 위해 사용돼요. 안전한 값으로 설정해 주세요.|
|
||||||
|`JWT_ACCESS_TOKEN_EXPIRES`||`5m`|Access Token의 유효 시간이에요.|
|
|`SESSION_EXPIRES`||`14d`|Session의 유효 시간이에요. Session은 마지막으로 사용된 후 설정된 유효 시간이 지나면 자동으로 삭제돼요.|
|
||||||
|`JWT_REFRESH_TOKEN_EXPIRES`||`14d`|Refresh Token의 유효 시간이에요.|
|
|
||||||
|`USER_CLIENT_CHALLENGE_EXPIRES`||`5m`|암호 키를 서버에 처음 등록할 때 사용되는 챌린지의 유효 시간이에요.|
|
|`USER_CLIENT_CHALLENGE_EXPIRES`||`5m`|암호 키를 서버에 처음 등록할 때 사용되는 챌린지의 유효 시간이에요.|
|
||||||
|`TOKEN_UPGRADE_CHALLENGE_EXPIRES`||`5m`|암호 키와 함께 로그인할 때 사용되는 챌린지의 유효 시간이에요.|
|
|`SESSION_UPGRADE_CHALLENGE_EXPIRES`||`5m`|암호 키와 함께 로그인할 때 사용되는 챌린지의 유효 시간이에요.|
|
||||||
|`TRUST_PROXY`|||신뢰할 수 있는 리버스 프록시의 수예요. 설정할 경우 1 이상의 정수로 설정해 주세요. 프록시에서 `X-Forwarded-For` HTTP 헤더를 올바르게 설정하도록 구성해 주세요.|
|
|`TRUST_PROXY`|||신뢰할 수 있는 리버스 프록시의 수예요. 설정할 경우 1 이상의 정수로 설정해 주세요. 프록시에서 `X-Forwarded-For` HTTP 헤더를 올바르게 설정하도록 구성해 주세요.|
|
||||||
|`NODE_ENV`||`production`|ArkVault의 사용 용도예요. `production`인 경우, 컨테이너가 실행될 때마다 DB 마이그레이션이 자동으로 실행돼요.|
|
|`NODE_ENV`||`production`|ArkVault의 사용 용도예요. `production`인 경우, 컨테이너가 실행될 때마다 DB 마이그레이션이 자동으로 실행돼요.|
|
||||||
|`PORT`||`80`|ArkVault 서버의 포트예요.|
|
|`PORT`||`80`|ArkVault 서버의 포트예요.|
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ CREATE TABLE `user_client_challenge` (
|
|||||||
`id` integer PRIMARY KEY NOT NULL,
|
`id` integer PRIMARY KEY NOT NULL,
|
||||||
`user_id` integer NOT NULL,
|
`user_id` integer NOT NULL,
|
||||||
`client_id` integer NOT NULL,
|
`client_id` integer NOT NULL,
|
||||||
`challenge` text NOT NULL,
|
`answer` text NOT NULL,
|
||||||
`allowed_ip` text NOT NULL,
|
`allowed_ip` text NOT NULL,
|
||||||
`expires_at` integer NOT NULL,
|
`expires_at` integer NOT NULL,
|
||||||
`is_used` integer DEFAULT false NOT NULL,
|
|
||||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action
|
FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
|
FOREIGN KEY (`user_id`,`client_id`) REFERENCES `user_client`(`user_id`,`client_id`) ON UPDATE no action ON DELETE no action
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
CREATE TABLE `directory` (
|
CREATE TABLE `directory` (
|
||||||
@@ -80,24 +80,26 @@ CREATE TABLE `master_encryption_key` (
|
|||||||
FOREIGN KEY (`created_by`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action
|
FOREIGN KEY (`created_by`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
CREATE TABLE `refresh_token` (
|
CREATE TABLE `session` (
|
||||||
`id` text PRIMARY KEY NOT NULL,
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
`user_id` integer NOT NULL,
|
`user_id` integer NOT NULL,
|
||||||
`client_id` integer,
|
`client_id` integer,
|
||||||
`expires_at` integer NOT NULL,
|
`created_at` integer NOT NULL,
|
||||||
|
`last_used_at` integer NOT NULL,
|
||||||
|
`last_used_by_ip` text,
|
||||||
|
`last_used_by_user_agent` text,
|
||||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action
|
FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
CREATE TABLE `token_upgrade_challenge` (
|
CREATE TABLE `session_upgrade_challenge` (
|
||||||
`id` integer PRIMARY KEY NOT NULL,
|
`id` integer PRIMARY KEY NOT NULL,
|
||||||
`refresh_token_id` text NOT NULL,
|
`session_id` text NOT NULL,
|
||||||
`client_id` integer NOT NULL,
|
`client_id` integer NOT NULL,
|
||||||
`challenge` text NOT NULL,
|
`answer` text NOT NULL,
|
||||||
`allowed_ip` text NOT NULL,
|
`allowed_ip` text NOT NULL,
|
||||||
`expires_at` integer NOT NULL,
|
`expires_at` integer NOT NULL,
|
||||||
`is_used` integer DEFAULT false NOT NULL,
|
FOREIGN KEY (`session_id`) REFERENCES `session`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
FOREIGN KEY (`refresh_token_id`) REFERENCES `refresh_token`(`id`) ON UPDATE no action ON DELETE no action,
|
|
||||||
FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action
|
FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
@@ -110,10 +112,11 @@ CREATE TABLE `user` (
|
|||||||
CREATE UNIQUE INDEX `client_encryption_public_key_unique` ON `client` (`encryption_public_key`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `client_encryption_public_key_unique` ON `client` (`encryption_public_key`);--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `client_signature_public_key_unique` ON `client` (`signature_public_key`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `client_signature_public_key_unique` ON `client` (`signature_public_key`);--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `client_encryption_public_key_signature_public_key_unique` ON `client` (`encryption_public_key`,`signature_public_key`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `client_encryption_public_key_signature_public_key_unique` ON `client` (`encryption_public_key`,`signature_public_key`);--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `user_client_challenge_challenge_unique` ON `user_client_challenge` (`challenge`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `user_client_challenge_answer_unique` ON `user_client_challenge` (`answer`);--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `directory_encrypted_data_encryption_key_unique` ON `directory` (`encrypted_data_encryption_key`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `directory_encrypted_data_encryption_key_unique` ON `directory` (`encrypted_data_encryption_key`);--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `file_path_unique` ON `file` (`path`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `file_path_unique` ON `file` (`path`);--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `file_encrypted_data_encryption_key_unique` ON `file` (`encrypted_data_encryption_key`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `file_encrypted_data_encryption_key_unique` ON `file` (`encrypted_data_encryption_key`);--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `refresh_token_user_id_client_id_unique` ON `refresh_token` (`user_id`,`client_id`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `session_user_id_client_id_unique` ON `session` (`user_id`,`client_id`);--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `token_upgrade_challenge_challenge_unique` ON `token_upgrade_challenge` (`challenge`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `session_upgrade_challenge_session_id_unique` ON `session_upgrade_challenge` (`session_id`);--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `session_upgrade_challenge_answer_unique` ON `session_upgrade_challenge` (`answer`);--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);
|
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "929c6bca-d0c0-4899-afc6-a0a498226f28",
|
"id": "c518e1b4-38f8-4c8e-bdc9-64152ab456d8",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"tables": {
|
"tables": {
|
||||||
"client": {
|
"client": {
|
||||||
@@ -147,8 +147,8 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"challenge": {
|
"answer": {
|
||||||
"name": "challenge",
|
"name": "answer",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
@@ -167,21 +167,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
|
||||||
"is_used": {
|
|
||||||
"name": "is_used",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"user_client_challenge_challenge_unique": {
|
"user_client_challenge_answer_unique": {
|
||||||
"name": "user_client_challenge_challenge_unique",
|
"name": "user_client_challenge_answer_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
"challenge"
|
"answer"
|
||||||
],
|
],
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
@@ -212,6 +204,21 @@
|
|||||||
],
|
],
|
||||||
"onDelete": "no action",
|
"onDelete": "no action",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": {
|
||||||
|
"name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk",
|
||||||
|
"tableFrom": "user_client_challenge",
|
||||||
|
"tableTo": "user_client",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id",
|
||||||
|
"client_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"user_id",
|
||||||
|
"client_id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compositePrimaryKeys": {},
|
"compositePrimaryKeys": {},
|
||||||
@@ -656,8 +663,8 @@
|
|||||||
},
|
},
|
||||||
"uniqueConstraints": {}
|
"uniqueConstraints": {}
|
||||||
},
|
},
|
||||||
"refresh_token": {
|
"session": {
|
||||||
"name": "refresh_token",
|
"name": "session",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -680,17 +687,38 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"expires_at": {
|
"created_at": {
|
||||||
"name": "expires_at",
|
"name": "created_at",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_used_at": {
|
||||||
|
"name": "last_used_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_used_by_ip": {
|
||||||
|
"name": "last_used_by_ip",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_used_by_user_agent": {
|
||||||
|
"name": "last_used_by_user_agent",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"refresh_token_user_id_client_id_unique": {
|
"session_user_id_client_id_unique": {
|
||||||
"name": "refresh_token_user_id_client_id_unique",
|
"name": "session_user_id_client_id_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
"user_id",
|
"user_id",
|
||||||
"client_id"
|
"client_id"
|
||||||
@@ -699,9 +727,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"refresh_token_user_id_user_id_fk": {
|
"session_user_id_user_id_fk": {
|
||||||
"name": "refresh_token_user_id_user_id_fk",
|
"name": "session_user_id_user_id_fk",
|
||||||
"tableFrom": "refresh_token",
|
"tableFrom": "session",
|
||||||
"tableTo": "user",
|
"tableTo": "user",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"user_id"
|
"user_id"
|
||||||
@@ -712,9 +740,9 @@
|
|||||||
"onDelete": "no action",
|
"onDelete": "no action",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"refresh_token_client_id_client_id_fk": {
|
"session_client_id_client_id_fk": {
|
||||||
"name": "refresh_token_client_id_client_id_fk",
|
"name": "session_client_id_client_id_fk",
|
||||||
"tableFrom": "refresh_token",
|
"tableFrom": "session",
|
||||||
"tableTo": "client",
|
"tableTo": "client",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"client_id"
|
"client_id"
|
||||||
@@ -729,8 +757,8 @@
|
|||||||
"compositePrimaryKeys": {},
|
"compositePrimaryKeys": {},
|
||||||
"uniqueConstraints": {}
|
"uniqueConstraints": {}
|
||||||
},
|
},
|
||||||
"token_upgrade_challenge": {
|
"session_upgrade_challenge": {
|
||||||
"name": "token_upgrade_challenge",
|
"name": "session_upgrade_challenge",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -739,8 +767,8 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"refresh_token_id": {
|
"session_id": {
|
||||||
"name": "refresh_token_id",
|
"name": "session_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
@@ -753,8 +781,8 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"challenge": {
|
"answer": {
|
||||||
"name": "challenge",
|
"name": "answer",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
@@ -773,32 +801,31 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
|
||||||
"is_used": {
|
|
||||||
"name": "is_used",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
"token_upgrade_challenge_challenge_unique": {
|
"session_upgrade_challenge_session_id_unique": {
|
||||||
"name": "token_upgrade_challenge_challenge_unique",
|
"name": "session_upgrade_challenge_session_id_unique",
|
||||||
"columns": [
|
"columns": [
|
||||||
"challenge"
|
"session_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"session_upgrade_challenge_answer_unique": {
|
||||||
|
"name": "session_upgrade_challenge_answer_unique",
|
||||||
|
"columns": [
|
||||||
|
"answer"
|
||||||
],
|
],
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"token_upgrade_challenge_refresh_token_id_refresh_token_id_fk": {
|
"session_upgrade_challenge_session_id_session_id_fk": {
|
||||||
"name": "token_upgrade_challenge_refresh_token_id_refresh_token_id_fk",
|
"name": "session_upgrade_challenge_session_id_session_id_fk",
|
||||||
"tableFrom": "token_upgrade_challenge",
|
"tableFrom": "session_upgrade_challenge",
|
||||||
"tableTo": "refresh_token",
|
"tableTo": "session",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"refresh_token_id"
|
"session_id"
|
||||||
],
|
],
|
||||||
"columnsTo": [
|
"columnsTo": [
|
||||||
"id"
|
"id"
|
||||||
@@ -806,9 +833,9 @@
|
|||||||
"onDelete": "no action",
|
"onDelete": "no action",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
"token_upgrade_challenge_client_id_client_id_fk": {
|
"session_upgrade_challenge_client_id_client_id_fk": {
|
||||||
"name": "token_upgrade_challenge_client_id_client_id_fk",
|
"name": "session_upgrade_challenge_client_id_client_id_fk",
|
||||||
"tableFrom": "token_upgrade_challenge",
|
"tableFrom": "session_upgrade_challenge",
|
||||||
"tableTo": "client",
|
"tableTo": "client",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"client_id"
|
"client_id"
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1736170919561,
|
"when": 1736637983139,
|
||||||
"tag": "0000_handy_captain_marvel",
|
"tag": "0000_spooky_lady_bullseye",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,35 +1,11 @@
|
|||||||
export const refreshToken = async (fetchInternal = fetch) => {
|
export const callGetApi = async (input: RequestInfo, fetchInternal = fetch) => {
|
||||||
return await fetchInternal("/api/auth/refreshToken", { method: "POST" });
|
return await fetchInternal(input);
|
||||||
};
|
};
|
||||||
|
|
||||||
const callApi = async (input: RequestInfo, init?: RequestInit, fetchInternal = fetch) => {
|
export const callPostApi = async <T>(input: RequestInfo, payload?: T, fetchInternal = fetch) => {
|
||||||
let res = await fetchInternal(input, init);
|
return await fetchInternal(input, {
|
||||||
if (res.status === 401) {
|
method: "POST",
|
||||||
res = await refreshToken();
|
headers: { "Content-Type": "application/json" },
|
||||||
if (!res.ok) {
|
body: payload ? JSON.stringify(payload) : undefined,
|
||||||
return res;
|
});
|
||||||
}
|
|
||||||
res = await fetchInternal(input, init);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const callGetApi = async (input: RequestInfo, fetchInternal?: typeof fetch) => {
|
|
||||||
return await callApi(input, undefined, fetchInternal);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const callPostApi = async <T>(
|
|
||||||
input: RequestInfo,
|
|
||||||
payload?: T,
|
|
||||||
fetchInternal?: typeof fetch,
|
|
||||||
) => {
|
|
||||||
return await callApi(
|
|
||||||
input,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: payload ? JSON.stringify(payload) : undefined,
|
|
||||||
},
|
|
||||||
fetchInternal,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { error, redirect, type Handle } from "@sveltejs/kit";
|
import { error, redirect, type Handle } from "@sveltejs/kit";
|
||||||
import { authenticate, AuthenticationError } from "$lib/server/modules/auth";
|
import { authenticate, AuthenticationError } from "$lib/server/modules/auth";
|
||||||
|
|
||||||
const whitelist = ["/auth/login", "/api/auth/login"];
|
|
||||||
|
|
||||||
export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
|
export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
|
||||||
const { pathname, search } = event.url;
|
const { pathname, search } = event.url;
|
||||||
if (whitelist.some((path) => pathname.startsWith(path))) {
|
if (pathname === "/api/auth/login") {
|
||||||
return await resolve(event);
|
return await resolve(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +17,9 @@ export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
|
|||||||
event.locals.session = await authenticate(sessionIdSigned, ip, userAgent);
|
event.locals.session = await authenticate(sessionIdSigned, ip, userAgent);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof AuthenticationError) {
|
if (e instanceof AuthenticationError) {
|
||||||
if (pathname.startsWith("/api")) {
|
if (pathname === "/auth/login") {
|
||||||
|
return await resolve(event);
|
||||||
|
} else if (pathname.startsWith("/api")) {
|
||||||
error(e.status, e.message);
|
error(e.status, e.message);
|
||||||
} else {
|
} else {
|
||||||
redirect(302, "/auth/login?redirect=" + encodeURIComponent(pathname + search));
|
redirect(302, "/auth/login?redirect=" + encodeURIComponent(pathname + search));
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const verifyClientEncMekSig = async (
|
|||||||
) => {
|
) => {
|
||||||
const userClient = await getUserClientWithDetails(userId, clientId);
|
const userClient = await getUserClientWithDetails(userId, clientId);
|
||||||
if (!userClient) {
|
if (!userClient) {
|
||||||
error(500, "Invalid access token");
|
error(500, "Invalid session id");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = JSON.stringify({ version, key: encMek });
|
const data = JSON.stringify({ version, key: encMek });
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const verifyUserClient = async (
|
|||||||
export const getUserClientStatus = async (userId: number, clientId: number) => {
|
export const getUserClientStatus = async (userId: number, clientId: number) => {
|
||||||
const userClient = await getUserClient(userId, clientId);
|
const userClient = await getUserClient(userId, clientId);
|
||||||
if (!userClient) {
|
if (!userClient) {
|
||||||
error(500, "Invalid access token");
|
error(500, "Invalid session id");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,37 +1,30 @@
|
|||||||
|
import { callPostApi } from "$lib/hooks";
|
||||||
import { encodeToBase64, decryptChallenge, signMessage } from "$lib/modules/crypto";
|
import { encodeToBase64, decryptChallenge, signMessage } from "$lib/modules/crypto";
|
||||||
import type {
|
import type {
|
||||||
TokenUpgradeRequest,
|
SessionUpgradeRequest,
|
||||||
TokenUpgradeResponse,
|
SessionUpgradeResponse,
|
||||||
TokenUpgradeVerifyRequest,
|
SessionUpgradeVerifyRequest,
|
||||||
} from "$lib/server/schemas";
|
} from "$lib/server/schemas";
|
||||||
|
|
||||||
export const requestTokenUpgrade = async (
|
export const requestSessionUpgrade = async (
|
||||||
encryptKeyBase64: string,
|
encryptKeyBase64: string,
|
||||||
decryptKey: CryptoKey,
|
decryptKey: CryptoKey,
|
||||||
verifyKeyBase64: string,
|
verifyKeyBase64: string,
|
||||||
signKey: CryptoKey,
|
signKey: CryptoKey,
|
||||||
) => {
|
) => {
|
||||||
let res = await fetch("/api/auth/upgradeToken", {
|
let res = await callPostApi<SessionUpgradeRequest>("/api/auth/upgradeSession", {
|
||||||
method: "POST",
|
encPubKey: encryptKeyBase64,
|
||||||
headers: { "Content-Type": "application/json" },
|
sigPubKey: verifyKeyBase64,
|
||||||
body: JSON.stringify({
|
|
||||||
encPubKey: encryptKeyBase64,
|
|
||||||
sigPubKey: verifyKeyBase64,
|
|
||||||
} satisfies TokenUpgradeRequest),
|
|
||||||
});
|
});
|
||||||
if (!res.ok) return false;
|
if (!res.ok) return false;
|
||||||
|
|
||||||
const { challenge }: TokenUpgradeResponse = await res.json();
|
const { challenge }: SessionUpgradeResponse = await res.json();
|
||||||
const answer = await decryptChallenge(challenge, decryptKey);
|
const answer = await decryptChallenge(challenge, decryptKey);
|
||||||
const answerSig = await signMessage(answer, signKey);
|
const answerSig = await signMessage(answer, signKey);
|
||||||
|
|
||||||
res = await fetch("/api/auth/upgradeToken/verify", {
|
res = await callPostApi<SessionUpgradeVerifyRequest>("/api/auth/upgradeSession/verify", {
|
||||||
method: "POST",
|
answer: encodeToBase64(answer),
|
||||||
headers: { "Content-Type": "application/json" },
|
answerSig: encodeToBase64(answerSig),
|
||||||
body: JSON.stringify({
|
|
||||||
answer: encodeToBase64(answer),
|
|
||||||
answerSig: encodeToBase64(answerSig),
|
|
||||||
} satisfies TokenUpgradeVerifyRequest),
|
|
||||||
});
|
});
|
||||||
return res.ok;
|
return res.ok;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { redirect } from "@sveltejs/kit";
|
import { redirect } from "@sveltejs/kit";
|
||||||
import type { PageServerLoad } from "./$types";
|
import type { PageServerLoad } from "./$types";
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ url, cookies }) => {
|
export const load: PageServerLoad = async ({ locals, url }) => {
|
||||||
const redirectPath = url.searchParams.get("redirect") || "/home";
|
const redirectPath = url.searchParams.get("redirect") || "/home";
|
||||||
|
|
||||||
const accessToken = cookies.get("accessToken");
|
if (locals.session) {
|
||||||
if (accessToken) {
|
|
||||||
redirect(302, redirectPath);
|
redirect(302, redirectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { Button, TextButton } from "$lib/components/buttons";
|
import { Button, TextButton } from "$lib/components/buttons";
|
||||||
import { TitleDiv, BottomDiv } from "$lib/components/divs";
|
import { TitleDiv, BottomDiv } from "$lib/components/divs";
|
||||||
import { TextInput } from "$lib/components/inputs";
|
import { TextInput } from "$lib/components/inputs";
|
||||||
import { refreshToken } from "$lib/hooks";
|
|
||||||
import { clientKeyStore, masterKeyStore } from "$lib/stores";
|
import { clientKeyStore, masterKeyStore } from "$lib/stores";
|
||||||
import { requestLogin, requestTokenUpgrade, requestMasterKeyDownload } from "./service";
|
import { requestLogin, requestSessionUpgrade, requestMasterKeyDownload } from "./service";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
@@ -25,7 +23,8 @@
|
|||||||
|
|
||||||
if (!$clientKeyStore) return await redirect("/key/generate");
|
if (!$clientKeyStore) return await redirect("/key/generate");
|
||||||
|
|
||||||
if (!(await requestTokenUpgrade($clientKeyStore))) throw new Error("Failed to upgrade token");
|
if (!(await requestSessionUpgrade($clientKeyStore)))
|
||||||
|
throw new Error("Failed to upgrade session");
|
||||||
|
|
||||||
// TODO: Multi-user support
|
// TODO: Multi-user support
|
||||||
|
|
||||||
@@ -42,13 +41,6 @@
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const res = await refreshToken();
|
|
||||||
if (res.ok) {
|
|
||||||
await goto(data.redirectPath, { replaceState: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
|
import { callPostApi } from "$lib/hooks";
|
||||||
import { exportRSAKeyToBase64 } from "$lib/modules/crypto";
|
import { exportRSAKeyToBase64 } from "$lib/modules/crypto";
|
||||||
import type { LoginRequest } from "$lib/server/schemas";
|
import type { LoginRequest } from "$lib/server/schemas";
|
||||||
import { requestTokenUpgrade as requestTokenUpgradeInternal } from "$lib/services/auth";
|
import { requestSessionUpgrade as requestSessionUpgradeInternal } from "$lib/services/auth";
|
||||||
import { requestClientRegistration } from "$lib/services/key";
|
import { requestClientRegistration } from "$lib/services/key";
|
||||||
import type { ClientKeys } from "$lib/stores";
|
import type { ClientKeys } from "$lib/stores";
|
||||||
|
|
||||||
export { requestMasterKeyDownload } from "$lib/services/key";
|
export { requestMasterKeyDownload } from "$lib/services/key";
|
||||||
|
|
||||||
export const requestLogin = async (email: string, password: string) => {
|
export const requestLogin = async (email: string, password: string) => {
|
||||||
const res = await fetch("/api/auth/login", {
|
const res = await callPostApi<LoginRequest>("/api/auth/login", { email, password });
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ email, password } satisfies LoginRequest),
|
|
||||||
});
|
|
||||||
return res.ok;
|
return res.ok;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestTokenUpgrade = async ({
|
export const requestSessionUpgrade = async ({
|
||||||
encryptKey,
|
encryptKey,
|
||||||
decryptKey,
|
decryptKey,
|
||||||
signKey,
|
signKey,
|
||||||
@@ -23,12 +20,12 @@ export const requestTokenUpgrade = async ({
|
|||||||
}: ClientKeys) => {
|
}: ClientKeys) => {
|
||||||
const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey);
|
const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey);
|
||||||
const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey);
|
const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey);
|
||||||
if (await requestTokenUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) {
|
if (await requestSessionUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) {
|
if (await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) {
|
||||||
return await requestTokenUpgradeInternal(
|
return await requestSessionUpgradeInternal(
|
||||||
encryptKeyBase64,
|
encryptKeyBase64,
|
||||||
decryptKey,
|
decryptKey,
|
||||||
verifyKeyBase64,
|
verifyKeyBase64,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
serializeClientKeys,
|
serializeClientKeys,
|
||||||
requestClientRegistration,
|
requestClientRegistration,
|
||||||
storeClientKeys,
|
storeClientKeys,
|
||||||
requestTokenUpgrade,
|
requestSessionUpgrade,
|
||||||
requestInitialMasterKeyRegistration,
|
requestInitialMasterKeyRegistration,
|
||||||
} from "./service";
|
} from "./service";
|
||||||
|
|
||||||
@@ -59,14 +59,14 @@
|
|||||||
await storeClientKeys($clientKeyStore);
|
await storeClientKeys($clientKeyStore);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(await requestTokenUpgrade(
|
!(await requestSessionUpgrade(
|
||||||
data.encryptKeyBase64,
|
data.encryptKeyBase64,
|
||||||
$clientKeyStore.decryptKey,
|
$clientKeyStore.decryptKey,
|
||||||
data.verifyKeyBase64,
|
data.verifyKeyBase64,
|
||||||
$clientKeyStore.signKey,
|
$clientKeyStore.signKey,
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
throw new Error("Failed to upgrade token");
|
throw new Error("Failed to upgrade session");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(await requestInitialMasterKeyRegistration(data.masterKeyWrapped, $clientKeyStore.signKey))
|
!(await requestInitialMasterKeyRegistration(data.masterKeyWrapped, $clientKeyStore.signKey))
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { signMasterKeyWrapped } from "$lib/modules/crypto";
|
|||||||
import type { InitialMasterKeyRegisterRequest } from "$lib/server/schemas";
|
import type { InitialMasterKeyRegisterRequest } from "$lib/server/schemas";
|
||||||
import type { ClientKeys } from "$lib/stores";
|
import type { ClientKeys } from "$lib/stores";
|
||||||
|
|
||||||
export { requestTokenUpgrade } from "$lib/services/auth";
|
export { requestSessionUpgrade } from "$lib/services/auth";
|
||||||
export { requestClientRegistration } from "$lib/services/key";
|
export { requestClientRegistration } from "$lib/services/key";
|
||||||
|
|
||||||
type SerializedKeyPairs = {
|
type SerializedKeyPairs = {
|
||||||
|
|||||||
Reference in New Issue
Block a user