mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-16 23:18:48 +00:00
214 lines
5.7 KiB
TypeScript
214 lines
5.7 KiB
TypeScript
import pg from "pg";
|
|
import { IntegrityError } from "./error";
|
|
import db from "./kysely";
|
|
import type { UserClientState } from "./schema";
|
|
|
|
interface Client {
|
|
id: number;
|
|
encPubKey: string;
|
|
sigPubKey: string;
|
|
}
|
|
|
|
interface UserClient {
|
|
userId: number;
|
|
clientId: number;
|
|
state: UserClientState;
|
|
}
|
|
|
|
interface UserClientWithDetails extends UserClient {
|
|
encPubKey: string;
|
|
sigPubKey: string;
|
|
}
|
|
|
|
export const createClient = async (encPubKey: string, sigPubKey: string, userId: number) => {
|
|
return await db
|
|
.transaction()
|
|
.setIsolationLevel("serializable")
|
|
.execute(async (trx) => {
|
|
const client = await trx
|
|
.selectFrom("client")
|
|
.where((eb) =>
|
|
eb.or([
|
|
eb("encryption_public_key", "=", encPubKey),
|
|
eb("encryption_public_key", "=", sigPubKey),
|
|
eb("signature_public_key", "=", encPubKey),
|
|
eb("signature_public_key", "=", sigPubKey),
|
|
]),
|
|
)
|
|
.limit(1)
|
|
.executeTakeFirst();
|
|
if (client) {
|
|
throw new IntegrityError("Public key(s) already registered");
|
|
}
|
|
|
|
const { clientId } = await trx
|
|
.insertInto("client")
|
|
.values({ encryption_public_key: encPubKey, signature_public_key: sigPubKey })
|
|
.returning("id as clientId")
|
|
.executeTakeFirstOrThrow();
|
|
await trx
|
|
.insertInto("user_client")
|
|
.values({ user_id: userId, client_id: clientId })
|
|
.execute();
|
|
return { id: clientId };
|
|
});
|
|
};
|
|
|
|
export const getClient = async (clientId: number) => {
|
|
const client = await db
|
|
.selectFrom("client")
|
|
.selectAll()
|
|
.where("id", "=", clientId)
|
|
.limit(1)
|
|
.executeTakeFirst();
|
|
return client
|
|
? ({
|
|
id: client.id,
|
|
encPubKey: client.encryption_public_key,
|
|
sigPubKey: client.signature_public_key,
|
|
} satisfies Client)
|
|
: null;
|
|
};
|
|
|
|
export const getClientByPubKeys = async (encPubKey: string, sigPubKey: string) => {
|
|
const client = await db
|
|
.selectFrom("client")
|
|
.selectAll()
|
|
.where("encryption_public_key", "=", encPubKey)
|
|
.where("signature_public_key", "=", sigPubKey)
|
|
.limit(1)
|
|
.executeTakeFirst();
|
|
return client
|
|
? ({
|
|
id: client.id,
|
|
encPubKey: client.encryption_public_key,
|
|
sigPubKey: client.signature_public_key,
|
|
} satisfies Client)
|
|
: null;
|
|
};
|
|
|
|
export const createUserClient = async (userId: number, clientId: number) => {
|
|
try {
|
|
await db.insertInto("user_client").values({ user_id: userId, client_id: clientId }).execute();
|
|
} catch (e) {
|
|
if (e instanceof pg.DatabaseError && e.code === "23505") {
|
|
throw new IntegrityError("User client already exists");
|
|
}
|
|
throw e;
|
|
}
|
|
};
|
|
|
|
export const getAllUserClients = async (userId: number) => {
|
|
const userClients = await db
|
|
.selectFrom("user_client")
|
|
.selectAll()
|
|
.where("user_id", "=", userId)
|
|
.execute();
|
|
return userClients.map(
|
|
({ user_id, client_id, state }) =>
|
|
({
|
|
userId: user_id,
|
|
clientId: client_id,
|
|
state,
|
|
}) satisfies UserClient,
|
|
);
|
|
};
|
|
|
|
export const getUserClient = async (userId: number, clientId: number) => {
|
|
const userClient = await db
|
|
.selectFrom("user_client")
|
|
.selectAll()
|
|
.where("user_id", "=", userId)
|
|
.where("client_id", "=", clientId)
|
|
.limit(1)
|
|
.executeTakeFirst();
|
|
return userClient
|
|
? ({
|
|
userId: userClient.user_id,
|
|
clientId: userClient.client_id,
|
|
state: userClient.state,
|
|
} satisfies UserClient)
|
|
: null;
|
|
};
|
|
|
|
export const getUserClientWithDetails = async (userId: number, clientId: number) => {
|
|
const userClient = await db
|
|
.selectFrom("user_client")
|
|
.innerJoin("client", "user_client.client_id", "client.id")
|
|
.selectAll()
|
|
.where("user_id", "=", userId)
|
|
.where("client_id", "=", clientId)
|
|
.limit(1)
|
|
.executeTakeFirst();
|
|
return userClient
|
|
? ({
|
|
userId: userClient.user_id,
|
|
clientId: userClient.client_id,
|
|
state: userClient.state,
|
|
encPubKey: userClient.encryption_public_key,
|
|
sigPubKey: userClient.signature_public_key,
|
|
} satisfies UserClientWithDetails)
|
|
: null;
|
|
};
|
|
|
|
export const setUserClientStateToPending = async (userId: number, clientId: number) => {
|
|
await db
|
|
.updateTable("user_client")
|
|
.set({ state: "pending" })
|
|
.where("user_id", "=", userId)
|
|
.where("client_id", "=", clientId)
|
|
.where("state", "=", "challenging")
|
|
.execute();
|
|
};
|
|
|
|
export const setUserClientStateToActive = async (userId: number, clientId: number) => {
|
|
await db
|
|
.updateTable("user_client")
|
|
.set({ state: "active" })
|
|
.where("user_id", "=", userId)
|
|
.where("client_id", "=", clientId)
|
|
.where("state", "=", "pending")
|
|
.execute();
|
|
};
|
|
|
|
export const registerUserClientChallenge = async (
|
|
userId: number,
|
|
clientId: number,
|
|
answer: string,
|
|
allowedIp: string,
|
|
expiresAt: Date,
|
|
) => {
|
|
const { id } = await db
|
|
.insertInto("user_client_challenge")
|
|
.values({
|
|
user_id: userId,
|
|
client_id: clientId,
|
|
answer,
|
|
allowed_ip: allowedIp,
|
|
expires_at: expiresAt,
|
|
})
|
|
.returning("id")
|
|
.executeTakeFirstOrThrow();
|
|
return { id };
|
|
};
|
|
|
|
export const consumeUserClientChallenge = async (
|
|
challengeId: number,
|
|
userId: number,
|
|
ip: string,
|
|
) => {
|
|
const challenge = await db
|
|
.deleteFrom("user_client_challenge")
|
|
.where("id", "=", challengeId)
|
|
.where("user_id", "=", userId)
|
|
.where("allowed_ip", "=", ip)
|
|
.where("expires_at", ">", new Date())
|
|
.returning(["client_id", "answer"])
|
|
.executeTakeFirst();
|
|
return challenge ? { clientId: challenge.client_id, answer: challenge.answer } : null;
|
|
};
|
|
|
|
export const cleanupExpiredUserClientChallenges = async () => {
|
|
await db.deleteFrom("user_client_challenge").where("expires_at", "<=", new Date()).execute();
|
|
};
|