mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-12 21:08:46 +00:00
강제 로그인 기능 추가
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
confirmText: string;
|
||||
isOpen: boolean;
|
||||
onbeforeclose?: () => void;
|
||||
oncancel?: () => void;
|
||||
onConfirmClick: ConfirmHandler;
|
||||
title: string;
|
||||
}
|
||||
@@ -22,6 +23,7 @@
|
||||
confirmText,
|
||||
isOpen = $bindable(),
|
||||
onbeforeclose,
|
||||
oncancel,
|
||||
onConfirmClick,
|
||||
title,
|
||||
}: Props = $props();
|
||||
@@ -31,6 +33,11 @@
|
||||
isOpen = false;
|
||||
};
|
||||
|
||||
const cancelAction = () => {
|
||||
oncancel?.();
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const confirmAction = async () => {
|
||||
if ((await onConfirmClick()) !== false) {
|
||||
closeModal();
|
||||
@@ -38,13 +45,13 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal bind:isOpen onclose={closeModal} class="space-y-4">
|
||||
<Modal bind:isOpen onclose={cancelAction} class="space-y-4">
|
||||
<div class="flex flex-col gap-y-2 break-keep">
|
||||
<p class="text-xl font-bold">{title}</p>
|
||||
{@render children()}
|
||||
</div>
|
||||
<div class="flex gap-x-2">
|
||||
<Button color="gray" onclick={closeModal} class="flex-1">{cancelText}</Button>
|
||||
<Button color="gray" onclick={cancelAction} class="flex-1">{cancelText}</Button>
|
||||
<Button onclick={confirmAction} class="flex-1">{confirmText}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -46,17 +46,32 @@ export const refreshSession = async (
|
||||
return { userId: session.user_id, clientId: session.client_id };
|
||||
};
|
||||
|
||||
export const upgradeSession = async (sessionId: string, clientId: number) => {
|
||||
export const upgradeSession = async (
|
||||
userId: number,
|
||||
sessionId: string,
|
||||
clientId: number,
|
||||
force: boolean,
|
||||
) => {
|
||||
try {
|
||||
const res = await db
|
||||
.updateTable("session")
|
||||
.set({ client_id: clientId })
|
||||
.where("id", "=", sessionId)
|
||||
.where("client_id", "is", null)
|
||||
.executeTakeFirst();
|
||||
if (res.numUpdatedRows === 0n) {
|
||||
throw new IntegrityError("Session not found");
|
||||
}
|
||||
await db.transaction().execute(async (trx) => {
|
||||
if (force) {
|
||||
await trx
|
||||
.deleteFrom("session")
|
||||
.where("id", "!=", sessionId)
|
||||
.where("user_id", "=", userId)
|
||||
.where("client_id", "=", clientId)
|
||||
.execute();
|
||||
}
|
||||
const res = await trx
|
||||
.updateTable("session")
|
||||
.set({ client_id: clientId })
|
||||
.where("id", "=", sessionId)
|
||||
.where("client_id", "is", null)
|
||||
.executeTakeFirst();
|
||||
if (res.numUpdatedRows === 0n) {
|
||||
throw new IntegrityError("Session not found");
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof pg.DatabaseError && e.code === "23505") {
|
||||
throw new IntegrityError("Session already exists");
|
||||
|
||||
@@ -4,28 +4,29 @@ export const passwordChangeRequest = z.object({
|
||||
oldPassword: z.string().trim().nonempty(),
|
||||
newPassword: z.string().trim().nonempty(),
|
||||
});
|
||||
export type PasswordChangeRequest = z.infer<typeof passwordChangeRequest>;
|
||||
export type PasswordChangeRequest = z.input<typeof passwordChangeRequest>;
|
||||
|
||||
export const loginRequest = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().trim().nonempty(),
|
||||
});
|
||||
export type LoginRequest = z.infer<typeof loginRequest>;
|
||||
export type LoginRequest = z.input<typeof loginRequest>;
|
||||
|
||||
export const sessionUpgradeRequest = z.object({
|
||||
encPubKey: z.string().base64().nonempty(),
|
||||
sigPubKey: z.string().base64().nonempty(),
|
||||
});
|
||||
export type SessionUpgradeRequest = z.infer<typeof sessionUpgradeRequest>;
|
||||
export type SessionUpgradeRequest = z.input<typeof sessionUpgradeRequest>;
|
||||
|
||||
export const sessionUpgradeResponse = z.object({
|
||||
id: z.number().int().positive(),
|
||||
challenge: z.string().base64().nonempty(),
|
||||
});
|
||||
export type SessionUpgradeResponse = z.infer<typeof sessionUpgradeResponse>;
|
||||
export type SessionUpgradeResponse = z.output<typeof sessionUpgradeResponse>;
|
||||
|
||||
export const sessionUpgradeVerifyRequest = z.object({
|
||||
id: z.number().int().positive(),
|
||||
answerSig: z.string().base64().nonempty(),
|
||||
force: z.boolean().default(false),
|
||||
});
|
||||
export type SessionUpgradeVerifyRequest = z.infer<typeof sessionUpgradeVerifyRequest>;
|
||||
export type SessionUpgradeVerifyRequest = z.input<typeof sessionUpgradeVerifyRequest>;
|
||||
|
||||
@@ -15,12 +15,12 @@ export const categoryInfoResponse = z.object({
|
||||
.optional(),
|
||||
subCategories: z.number().int().positive().array(),
|
||||
});
|
||||
export type CategoryInfoResponse = z.infer<typeof categoryInfoResponse>;
|
||||
export type CategoryInfoResponse = z.output<typeof categoryInfoResponse>;
|
||||
|
||||
export const categoryFileAddRequest = z.object({
|
||||
file: z.number().int().positive(),
|
||||
});
|
||||
export type CategoryFileAddRequest = z.infer<typeof categoryFileAddRequest>;
|
||||
export type CategoryFileAddRequest = z.input<typeof categoryFileAddRequest>;
|
||||
|
||||
export const categoryFileListResponse = z.object({
|
||||
files: z.array(
|
||||
@@ -30,19 +30,19 @@ export const categoryFileListResponse = z.object({
|
||||
}),
|
||||
),
|
||||
});
|
||||
export type CategoryFileListResponse = z.infer<typeof categoryFileListResponse>;
|
||||
export type CategoryFileListResponse = z.output<typeof categoryFileListResponse>;
|
||||
|
||||
export const categoryFileRemoveRequest = z.object({
|
||||
file: z.number().int().positive(),
|
||||
});
|
||||
export type CategoryFileRemoveRequest = z.infer<typeof categoryFileRemoveRequest>;
|
||||
export type CategoryFileRemoveRequest = z.input<typeof categoryFileRemoveRequest>;
|
||||
|
||||
export const categoryRenameRequest = z.object({
|
||||
dekVersion: z.string().datetime(),
|
||||
name: z.string().base64().nonempty(),
|
||||
nameIv: z.string().base64().nonempty(),
|
||||
});
|
||||
export type CategoryRenameRequest = z.infer<typeof categoryRenameRequest>;
|
||||
export type CategoryRenameRequest = z.input<typeof categoryRenameRequest>;
|
||||
|
||||
export const categoryCreateRequest = z.object({
|
||||
parent: categoryIdSchema,
|
||||
@@ -52,4 +52,4 @@ export const categoryCreateRequest = z.object({
|
||||
name: z.string().base64().nonempty(),
|
||||
nameIv: z.string().base64().nonempty(),
|
||||
});
|
||||
export type CategoryCreateRequest = z.infer<typeof categoryCreateRequest>;
|
||||
export type CategoryCreateRequest = z.input<typeof categoryCreateRequest>;
|
||||
|
||||
@@ -8,29 +8,29 @@ export const clientListResponse = z.object({
|
||||
}),
|
||||
),
|
||||
});
|
||||
export type ClientListResponse = z.infer<typeof clientListResponse>;
|
||||
export type ClientListResponse = z.output<typeof clientListResponse>;
|
||||
|
||||
export const clientRegisterRequest = z.object({
|
||||
encPubKey: z.string().base64().nonempty(),
|
||||
sigPubKey: z.string().base64().nonempty(),
|
||||
});
|
||||
export type ClientRegisterRequest = z.infer<typeof clientRegisterRequest>;
|
||||
export type ClientRegisterRequest = z.input<typeof clientRegisterRequest>;
|
||||
|
||||
export const clientRegisterResponse = z.object({
|
||||
id: z.number().int().positive(),
|
||||
challenge: z.string().base64().nonempty(),
|
||||
});
|
||||
export type ClientRegisterResponse = z.infer<typeof clientRegisterResponse>;
|
||||
export type ClientRegisterResponse = z.output<typeof clientRegisterResponse>;
|
||||
|
||||
export const clientRegisterVerifyRequest = z.object({
|
||||
id: z.number().int().positive(),
|
||||
answerSig: z.string().base64().nonempty(),
|
||||
});
|
||||
export type ClientRegisterVerifyRequest = z.infer<typeof clientRegisterVerifyRequest>;
|
||||
export type ClientRegisterVerifyRequest = z.input<typeof clientRegisterVerifyRequest>;
|
||||
|
||||
export const clientStatusResponse = z.object({
|
||||
id: z.number().int().positive(),
|
||||
state: z.enum(["pending", "active"]),
|
||||
isInitialMekNeeded: z.boolean(),
|
||||
});
|
||||
export type ClientStatusResponse = z.infer<typeof clientStatusResponse>;
|
||||
export type ClientStatusResponse = z.output<typeof clientStatusResponse>;
|
||||
|
||||
@@ -16,19 +16,19 @@ export const directoryInfoResponse = z.object({
|
||||
subDirectories: z.number().int().positive().array(),
|
||||
files: z.number().int().positive().array(),
|
||||
});
|
||||
export type DirectoryInfoResponse = z.infer<typeof directoryInfoResponse>;
|
||||
export type DirectoryInfoResponse = z.output<typeof directoryInfoResponse>;
|
||||
|
||||
export const directoryDeleteResponse = z.object({
|
||||
deletedFiles: z.number().int().positive().array(),
|
||||
});
|
||||
export type DirectoryDeleteResponse = z.infer<typeof directoryDeleteResponse>;
|
||||
export type DirectoryDeleteResponse = z.output<typeof directoryDeleteResponse>;
|
||||
|
||||
export const directoryRenameRequest = z.object({
|
||||
dekVersion: z.string().datetime(),
|
||||
name: z.string().base64().nonempty(),
|
||||
nameIv: z.string().base64().nonempty(),
|
||||
});
|
||||
export type DirectoryRenameRequest = z.infer<typeof directoryRenameRequest>;
|
||||
export type DirectoryRenameRequest = z.input<typeof directoryRenameRequest>;
|
||||
|
||||
export const directoryCreateRequest = z.object({
|
||||
parent: directoryIdSchema,
|
||||
@@ -38,4 +38,4 @@ export const directoryCreateRequest = z.object({
|
||||
name: z.string().base64().nonempty(),
|
||||
nameIv: z.string().base64().nonempty(),
|
||||
});
|
||||
export type DirectoryCreateRequest = z.infer<typeof directoryCreateRequest>;
|
||||
export type DirectoryCreateRequest = z.input<typeof directoryCreateRequest>;
|
||||
|
||||
@@ -21,42 +21,42 @@ export const fileInfoResponse = z.object({
|
||||
lastModifiedAtIv: z.string().base64().nonempty(),
|
||||
categories: z.number().int().positive().array(),
|
||||
});
|
||||
export type FileInfoResponse = z.infer<typeof fileInfoResponse>;
|
||||
export type FileInfoResponse = z.output<typeof fileInfoResponse>;
|
||||
|
||||
export const fileRenameRequest = z.object({
|
||||
dekVersion: z.string().datetime(),
|
||||
name: z.string().base64().nonempty(),
|
||||
nameIv: z.string().base64().nonempty(),
|
||||
});
|
||||
export type FileRenameRequest = z.infer<typeof fileRenameRequest>;
|
||||
export type FileRenameRequest = z.input<typeof fileRenameRequest>;
|
||||
|
||||
export const fileThumbnailInfoResponse = z.object({
|
||||
updatedAt: z.string().datetime(),
|
||||
contentIv: z.string().base64().nonempty(),
|
||||
});
|
||||
export type FileThumbnailInfoResponse = z.infer<typeof fileThumbnailInfoResponse>;
|
||||
export type FileThumbnailInfoResponse = z.output<typeof fileThumbnailInfoResponse>;
|
||||
|
||||
export const fileThumbnailUploadRequest = z.object({
|
||||
dekVersion: z.string().datetime(),
|
||||
contentIv: z.string().base64().nonempty(),
|
||||
});
|
||||
export type FileThumbnailUploadRequest = z.infer<typeof fileThumbnailUploadRequest>;
|
||||
export type FileThumbnailUploadRequest = z.input<typeof fileThumbnailUploadRequest>;
|
||||
|
||||
export const duplicateFileScanRequest = z.object({
|
||||
hskVersion: z.number().int().positive(),
|
||||
contentHmac: z.string().base64().nonempty(),
|
||||
});
|
||||
export type DuplicateFileScanRequest = z.infer<typeof duplicateFileScanRequest>;
|
||||
export type DuplicateFileScanRequest = z.input<typeof duplicateFileScanRequest>;
|
||||
|
||||
export const duplicateFileScanResponse = z.object({
|
||||
files: z.number().int().positive().array(),
|
||||
});
|
||||
export type DuplicateFileScanResponse = z.infer<typeof duplicateFileScanResponse>;
|
||||
export type DuplicateFileScanResponse = z.output<typeof duplicateFileScanResponse>;
|
||||
|
||||
export const missingThumbnailFileScanResponse = z.object({
|
||||
files: z.number().int().positive().array(),
|
||||
});
|
||||
export type MissingThumbnailFileScanResponse = z.infer<typeof missingThumbnailFileScanResponse>;
|
||||
export type MissingThumbnailFileScanResponse = z.output<typeof missingThumbnailFileScanResponse>;
|
||||
|
||||
export const fileUploadRequest = z.object({
|
||||
parent: directoryIdSchema,
|
||||
@@ -78,9 +78,9 @@ export const fileUploadRequest = z.object({
|
||||
lastModifiedAt: z.string().base64().nonempty(),
|
||||
lastModifiedAtIv: z.string().base64().nonempty(),
|
||||
});
|
||||
export type FileUploadRequest = z.infer<typeof fileUploadRequest>;
|
||||
export type FileUploadRequest = z.input<typeof fileUploadRequest>;
|
||||
|
||||
export const fileUploadResponse = z.object({
|
||||
file: z.number().int().positive(),
|
||||
});
|
||||
export type FileUploadResponse = z.infer<typeof fileUploadResponse>;
|
||||
export type FileUploadResponse = z.output<typeof fileUploadResponse>;
|
||||
|
||||
@@ -10,10 +10,10 @@ export const hmacSecretListResponse = z.object({
|
||||
}),
|
||||
),
|
||||
});
|
||||
export type HmacSecretListResponse = z.infer<typeof hmacSecretListResponse>;
|
||||
export type HmacSecretListResponse = z.output<typeof hmacSecretListResponse>;
|
||||
|
||||
export const initialHmacSecretRegisterRequest = z.object({
|
||||
mekVersion: z.number().int().positive(),
|
||||
hsk: z.string().base64().nonempty(),
|
||||
});
|
||||
export type InitialHmacSecretRegisterRequest = z.infer<typeof initialHmacSecretRegisterRequest>;
|
||||
export type InitialHmacSecretRegisterRequest = z.input<typeof initialHmacSecretRegisterRequest>;
|
||||
|
||||
@@ -10,10 +10,10 @@ export const masterKeyListResponse = z.object({
|
||||
}),
|
||||
),
|
||||
});
|
||||
export type MasterKeyListResponse = z.infer<typeof masterKeyListResponse>;
|
||||
export type MasterKeyListResponse = z.output<typeof masterKeyListResponse>;
|
||||
|
||||
export const initialMasterKeyRegisterRequest = z.object({
|
||||
mek: z.string().base64().nonempty(),
|
||||
mekSig: z.string().base64().nonempty(),
|
||||
});
|
||||
export type InitialMasterKeyRegisterRequest = z.infer<typeof initialMasterKeyRegisterRequest>;
|
||||
export type InitialMasterKeyRegisterRequest = z.input<typeof initialMasterKeyRegisterRequest>;
|
||||
|
||||
@@ -4,9 +4,9 @@ export const userInfoResponse = z.object({
|
||||
email: z.string().email(),
|
||||
nickname: z.string().nonempty(),
|
||||
});
|
||||
export type UserInfoResponse = z.infer<typeof userInfoResponse>;
|
||||
export type UserInfoResponse = z.output<typeof userInfoResponse>;
|
||||
|
||||
export const nicknameChangeRequest = z.object({
|
||||
newNickname: z.string().trim().min(2).max(8),
|
||||
});
|
||||
export type NicknameChangeRequest = z.infer<typeof nicknameChangeRequest>;
|
||||
export type NicknameChangeRequest = z.input<typeof nicknameChangeRequest>;
|
||||
|
||||
@@ -87,9 +87,11 @@ export const createSessionUpgradeChallenge = async (
|
||||
|
||||
export const verifySessionUpgradeChallenge = async (
|
||||
sessionId: string,
|
||||
userId: number,
|
||||
ip: string,
|
||||
challengeId: number,
|
||||
answerSig: string,
|
||||
force: boolean,
|
||||
) => {
|
||||
const challenge = await consumeSessionUpgradeChallenge(challengeId, sessionId, ip);
|
||||
if (!challenge) {
|
||||
@@ -106,13 +108,13 @@ export const verifySessionUpgradeChallenge = async (
|
||||
}
|
||||
|
||||
try {
|
||||
await upgradeSession(sessionId, client.id);
|
||||
await upgradeSession(userId, sessionId, client.id, force);
|
||||
} catch (e) {
|
||||
if (e instanceof IntegrityError) {
|
||||
if (e.message === "Session not found") {
|
||||
error(500, "Invalid challenge answer");
|
||||
} else if (e.message === "Session already exists") {
|
||||
error(403, "Already logged in");
|
||||
} else if (!force && e.message === "Session already exists") {
|
||||
error(409, "Already logged in");
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -11,12 +11,14 @@ export const requestSessionUpgrade = async (
|
||||
decryptKey: CryptoKey,
|
||||
verifyKeyBase64: string,
|
||||
signKey: CryptoKey,
|
||||
force = false,
|
||||
) => {
|
||||
let res = await callPostApi<SessionUpgradeRequest>("/api/auth/upgradeSession", {
|
||||
encPubKey: encryptKeyBase64,
|
||||
sigPubKey: verifyKeyBase64,
|
||||
});
|
||||
if (!res.ok) return false;
|
||||
if (res.status === 403) return [false, "Unregistered client"] as const;
|
||||
else if (!res.ok) return [false] as const;
|
||||
|
||||
const { id, challenge }: SessionUpgradeResponse = await res.json();
|
||||
const answer = await decryptChallenge(challenge, decryptKey);
|
||||
@@ -25,6 +27,13 @@ export const requestSessionUpgrade = async (
|
||||
res = await callPostApi<SessionUpgradeVerifyRequest>("/api/auth/upgradeSession/verify", {
|
||||
id,
|
||||
answerSig: encodeToBase64(answerSig),
|
||||
force,
|
||||
});
|
||||
if (res.status === 409) return [false, "Already logged in"] as const;
|
||||
else return [res.ok] as const;
|
||||
};
|
||||
|
||||
export const requestLogout = async () => {
|
||||
const res = await callPostApi("/api/auth/logout");
|
||||
return res.ok;
|
||||
};
|
||||
|
||||
@@ -3,10 +3,18 @@
|
||||
import { BottomDiv, Button, FullscreenDiv, TextButton, TextInput } from "$lib/components/atoms";
|
||||
import { TitledDiv } from "$lib/components/molecules";
|
||||
import { clientKeyStore, masterKeyStore } from "$lib/stores";
|
||||
import { requestLogin, requestSessionUpgrade, requestMasterKeyDownload } from "./service";
|
||||
import ForceLoginModal from "./ForceLoginModal.svelte";
|
||||
import {
|
||||
requestLogout,
|
||||
requestLogin,
|
||||
requestSessionUpgrade,
|
||||
requestMasterKeyDownload,
|
||||
} from "./service";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let isForceLoginModalOpen = $state(false);
|
||||
|
||||
let email = $state("");
|
||||
let password = $state("");
|
||||
|
||||
@@ -14,6 +22,32 @@
|
||||
return await goto(`${url}?redirect=${encodeURIComponent(data.redirectPath)}`);
|
||||
};
|
||||
|
||||
const upgradeSession = async (force: boolean) => {
|
||||
try {
|
||||
const [upgradeRes, upgradeError] = await requestSessionUpgrade($clientKeyStore!, force);
|
||||
if (!force && upgradeError === "Already logged in") {
|
||||
isForceLoginModalOpen = true;
|
||||
return;
|
||||
} else if (!upgradeRes) {
|
||||
throw new Error("Failed to upgrade session");
|
||||
}
|
||||
|
||||
// TODO: Multi-user support
|
||||
|
||||
if (
|
||||
$masterKeyStore ||
|
||||
(await requestMasterKeyDownload($clientKeyStore!.decryptKey, $clientKeyStore!.verifyKey))
|
||||
) {
|
||||
await goto(data.redirectPath);
|
||||
} else {
|
||||
await redirect("/client/pending");
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
const login = async () => {
|
||||
// TODO: Validation
|
||||
|
||||
@@ -22,19 +56,7 @@
|
||||
|
||||
if (!$clientKeyStore) return await redirect("/key/generate");
|
||||
|
||||
if (!(await requestSessionUpgrade($clientKeyStore)))
|
||||
throw new Error("Failed to upgrade session");
|
||||
|
||||
// TODO: Multi-user support
|
||||
|
||||
if (
|
||||
$masterKeyStore ||
|
||||
(await requestMasterKeyDownload($clientKeyStore.decryptKey, $clientKeyStore.verifyKey))
|
||||
) {
|
||||
await goto(data.redirectPath);
|
||||
} else {
|
||||
await redirect("/client/pending");
|
||||
}
|
||||
await upgradeSession(false);
|
||||
} catch (e) {
|
||||
// TODO: Alert
|
||||
throw e;
|
||||
@@ -63,3 +85,9 @@
|
||||
<TextButton>계정이 없어요</TextButton>
|
||||
</BottomDiv>
|
||||
</FullscreenDiv>
|
||||
|
||||
<ForceLoginModal
|
||||
bind:isOpen={isForceLoginModalOpen}
|
||||
oncancel={requestLogout}
|
||||
onLoginClick={() => upgradeSession(true)}
|
||||
/>
|
||||
|
||||
22
src/routes/(fullscreen)/auth/login/ForceLoginModal.svelte
Normal file
22
src/routes/(fullscreen)/auth/login/ForceLoginModal.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { ActionModal } from "$lib/components/molecules";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
oncancel: () => void;
|
||||
onLoginClick: () => void;
|
||||
}
|
||||
|
||||
let { isOpen = $bindable(), oncancel, onLoginClick }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ActionModal
|
||||
bind:isOpen
|
||||
title="다른 디바이스에 이미 로그인되어 있어요."
|
||||
cancelText="아니요"
|
||||
{oncancel}
|
||||
confirmText="네"
|
||||
onConfirmClick={onLoginClick}
|
||||
>
|
||||
<p>다른 디바이스에서는 로그아웃하고, 이 디바이스에서 로그인할까요?</p>
|
||||
</ActionModal>
|
||||
@@ -5,6 +5,7 @@ import { requestSessionUpgrade as requestSessionUpgradeInternal } from "$lib/ser
|
||||
import { requestClientRegistration } from "$lib/services/key";
|
||||
import type { ClientKeys } from "$lib/stores";
|
||||
|
||||
export { requestLogout } from "$lib/services/auth";
|
||||
export { requestMasterKeyDownload } from "$lib/services/key";
|
||||
|
||||
export const requestLogin = async (email: string, password: string) => {
|
||||
@@ -12,26 +13,33 @@ export const requestLogin = async (email: string, password: string) => {
|
||||
return res.ok;
|
||||
};
|
||||
|
||||
export const requestSessionUpgrade = async ({
|
||||
encryptKey,
|
||||
decryptKey,
|
||||
signKey,
|
||||
verifyKey,
|
||||
}: ClientKeys) => {
|
||||
export const requestSessionUpgrade = async (
|
||||
{ encryptKey, decryptKey, signKey, verifyKey }: ClientKeys,
|
||||
force: boolean,
|
||||
) => {
|
||||
const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey);
|
||||
const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey);
|
||||
if (await requestSessionUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) {
|
||||
return true;
|
||||
const [res, error] = await requestSessionUpgradeInternal(
|
||||
encryptKeyBase64,
|
||||
decryptKey,
|
||||
verifyKeyBase64,
|
||||
signKey,
|
||||
force,
|
||||
);
|
||||
if (error === undefined) return [res] as const;
|
||||
|
||||
if (
|
||||
error === "Unregistered client" &&
|
||||
!(await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey))
|
||||
) {
|
||||
return [false] as const;
|
||||
} else if (error === "Already logged in") {
|
||||
return [false, force ? undefined : error] as const;
|
||||
}
|
||||
|
||||
if (await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) {
|
||||
return await requestSessionUpgradeInternal(
|
||||
encryptKeyBase64,
|
||||
decryptKey,
|
||||
verifyKeyBase64,
|
||||
signKey,
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return [
|
||||
(
|
||||
await requestSessionUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)
|
||||
)[0],
|
||||
] as const;
|
||||
};
|
||||
|
||||
@@ -59,12 +59,14 @@
|
||||
await storeClientKeys($clientKeyStore);
|
||||
|
||||
if (
|
||||
!(await requestSessionUpgrade(
|
||||
data.encryptKeyBase64,
|
||||
$clientKeyStore.decryptKey,
|
||||
data.verifyKeyBase64,
|
||||
$clientKeyStore.signKey,
|
||||
))
|
||||
!(
|
||||
await requestSessionUpgrade(
|
||||
data.encryptKeyBase64,
|
||||
$clientKeyStore.decryptKey,
|
||||
data.verifyKeyBase64,
|
||||
$clientKeyStore.signKey,
|
||||
)
|
||||
)[0]
|
||||
)
|
||||
throw new Error("Failed to upgrade session");
|
||||
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
import { callPostApi } from "$lib/hooks";
|
||||
|
||||
export const requestLogout = async () => {
|
||||
const res = await callPostApi("/api/auth/logout");
|
||||
return res.ok;
|
||||
};
|
||||
export { requestLogout } from "$lib/services/auth";
|
||||
|
||||
@@ -5,12 +5,12 @@ import { verifySessionUpgradeChallenge } from "$lib/server/services/auth";
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
export const POST: RequestHandler = async ({ locals, request }) => {
|
||||
const { sessionId } = await authorize(locals, "notClient");
|
||||
const { sessionId, userId } = await authorize(locals, "notClient");
|
||||
|
||||
const zodRes = sessionUpgradeVerifyRequest.safeParse(await request.json());
|
||||
if (!zodRes.success) error(400, "Invalid request body");
|
||||
const { id, answerSig } = zodRes.data;
|
||||
const { id, answerSig, force } = zodRes.data;
|
||||
|
||||
await verifySessionUpgradeChallenge(sessionId, locals.ip, id, answerSig);
|
||||
await verifySessionUpgradeChallenge(sessionId, userId, locals.ip, id, answerSig, force);
|
||||
return text("Session upgraded", { headers: { "Content-Type": "text/plain" } });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user