feat: v2
This commit is contained in:
+109
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
chmod,
|
||||
mkdir,
|
||||
readFile,
|
||||
rename,
|
||||
rm,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
export type KuberSession = {
|
||||
token: string;
|
||||
expiresAt: string;
|
||||
user: {
|
||||
username: string;
|
||||
roles: string[];
|
||||
};
|
||||
};
|
||||
|
||||
function runtimeSessionPath(): string {
|
||||
const directory =
|
||||
process.env.XDG_RUNTIME_DIR ??
|
||||
join(tmpdir(), `kuber-${process.getuid?.() ?? "user"}`);
|
||||
return join(directory, "kuber", "session.json");
|
||||
}
|
||||
|
||||
function persistentSessionPath(): string {
|
||||
const directory =
|
||||
process.env.XDG_CONFIG_HOME ??
|
||||
join(process.env.HOME ?? tmpdir(), ".config");
|
||||
return join(directory, "kuber", "session.json");
|
||||
}
|
||||
|
||||
export function getSessionPath(persistent: boolean): string {
|
||||
return persistent ? persistentSessionPath() : runtimeSessionPath();
|
||||
}
|
||||
|
||||
function isSession(value: unknown): value is KuberSession {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const session = value as Partial<KuberSession>;
|
||||
return (
|
||||
typeof session.token === "string" &&
|
||||
typeof session.expiresAt === "string" &&
|
||||
Boolean(session.user) &&
|
||||
typeof session.user?.username === "string" &&
|
||||
Array.isArray(session.user.roles) &&
|
||||
session.user.roles.every((role) => typeof role === "string")
|
||||
);
|
||||
}
|
||||
|
||||
async function readSessionFile(
|
||||
path: string,
|
||||
): Promise<KuberSession | undefined> {
|
||||
try {
|
||||
const value: unknown = JSON.parse(await readFile(path, "utf8"));
|
||||
if (!isSession(value)) return;
|
||||
if (Date.parse(value.expiresAt) <= Date.now()) {
|
||||
await rm(path, { force: true });
|
||||
return;
|
||||
}
|
||||
return value;
|
||||
} catch (error) {
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"code" in error &&
|
||||
error.code === "ENOENT"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (error instanceof SyntaxError) return;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readSession(): Promise<KuberSession | undefined> {
|
||||
return (
|
||||
(await readSessionFile(runtimeSessionPath())) ??
|
||||
(await readSessionFile(persistentSessionPath()))
|
||||
);
|
||||
}
|
||||
|
||||
export async function writeSession(
|
||||
session: KuberSession,
|
||||
persistent: boolean,
|
||||
): Promise<string> {
|
||||
const path = getSessionPath(persistent);
|
||||
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
||||
await chmod(dirname(path), 0o700);
|
||||
const temporaryPath = `${path}.${randomUUID()}.tmp`;
|
||||
await writeFile(temporaryPath, `${JSON.stringify(session, null, 2)}\n`, {
|
||||
flag: "wx",
|
||||
mode: 0o600,
|
||||
});
|
||||
await rename(temporaryPath, path);
|
||||
await chmod(path, 0o600);
|
||||
await rm(getSessionPath(!persistent), { force: true });
|
||||
return path;
|
||||
}
|
||||
|
||||
export async function removeSessions(): Promise<void> {
|
||||
await Promise.all(
|
||||
[runtimeSessionPath(), persistentSessionPath()].map((path) =>
|
||||
rm(path, { force: true }),
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user