CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | /**
* SSH keys — CRUD coverage.
*
* `src/routes/settings.tsx` is a §4 LOCKED BLOCK, so these tests exercise the
* public HTTP surface via the app router and assert behavioural contracts
* (auth guard, accepted key formats, ownership enforcement, list shape) that
* must be preserved. All mutations are guarded by `requireAuth`, so without a
* real session the endpoints redirect to `/login` — that redirect is itself
* the auth-contract we test. DB-backed side-effects only execute when a
* `DATABASE_URL` is present; otherwise the handler degrades gracefully.
*
* Pure-logic tests (fingerprint derivation, key-format validator) replicate
* the same algorithms used inside the locked route so we catch regressions
* if the wire format ever changes without adding a test fixture.
*/
import { describe, it, expect } from "bun:test";
import app from "../app";
const HAS_DB = Boolean(process.env.DATABASE_URL);
// ---------------------------------------------------------------------------
// Pure helpers mirroring the locked route's validation logic. Kept in-file
// rather than imported (route is LOCKED, no __test export available).
// ---------------------------------------------------------------------------
function isValidSshKeyFormat(publicKey: string): boolean {
return (
publicKey.startsWith("ssh-rsa ") ||
publicKey.startsWith("ssh-ed25519 ") ||
publicKey.startsWith("ecdsa-sha2-")
);
}
async function computeFingerprint(publicKey: string): Promise<string> {
const keyData = publicKey.split(" ")[1] || "";
const hashBuffer = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(keyData)
);
return (
"SHA256:" +
btoa(String.fromCharCode(...new Uint8Array(hashBuffer))).replace(/=+$/, "")
);
}
// ---------------------------------------------------------------------------
// Pure logic
// ---------------------------------------------------------------------------
describe("ssh keys — accepted public-key formats", () => {
it("accepts ssh-ed25519 keys", () => {
expect(
isValidSshKeyFormat(
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEXAMPLEKEYBYTES alice@laptop"
)
).toBe(true);
});
it("accepts ssh-rsa keys", () => {
expect(isValidSshKeyFormat("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB")).toBe(
true
);
});
it("accepts ecdsa-sha2-* keys", () => {
expect(
isValidSshKeyFormat("ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlz")
).toBe(true);
});
it("rejects malformed / unsupported key prefixes", () => {
expect(isValidSshKeyFormat("")).toBe(false);
expect(isValidSshKeyFormat("not-a-key")).toBe(false);
expect(isValidSshKeyFormat("ssh-dss AAAAB3NzaC1kc3M=")).toBe(false);
// Case-sensitive prefix check — uppercase should be rejected.
expect(isValidSshKeyFormat("SSH-RSA AAAA")).toBe(false);
});
it("rejects keys with leading whitespace (contract preserves strict prefix)", () => {
expect(isValidSshKeyFormat(" ssh-ed25519 AAAA")).toBe(false);
});
});
describe("ssh keys — fingerprint shape", () => {
it("produces a SHA256:… fingerprint without padding", async () => {
const fp = await computeFingerprint(
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEXAMPLEBYTESHERE"
);
expect(fp.startsWith("SHA256:")).toBe(true);
expect(fp.endsWith("=")).toBe(false);
});
it("is deterministic for the same key body", async () => {
const k = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEDETERMINISTICBYTES";
expect(await computeFingerprint(k)).toBe(await computeFingerprint(k));
});
it("differs across distinct key bodies", async () => {
const a = await computeFingerprint("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAA");
const b = await computeFingerprint("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5BBBBB");
expect(a).not.toBe(b);
});
});
// ---------------------------------------------------------------------------
// Route auth contract (no session cookie)
// ---------------------------------------------------------------------------
describe("ssh keys — /settings/keys auth guard", () => {
it("GET /settings/keys without a session → redirect to /login", async () => {
const res = await app.request("/settings/keys");
expect(res.status).toBe(302);
expect(res.headers.get("location") || "").toContain("/login");
});
it("POST /settings/keys (add) without a session → redirect to /login", async () => {
const res = await app.request("/settings/keys", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
title: "laptop",
public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEXAMPLE alice",
}),
});
expect(res.status).toBe(302);
expect(res.headers.get("location") || "").toContain("/login");
});
it("POST /settings/keys/:id/delete without a session → redirect to /login", async () => {
const res = await app.request(
"/settings/keys/00000000-0000-0000-0000-000000000000/delete",
{ method: "POST" }
);
expect(res.status).toBe(302);
expect(res.headers.get("location") || "").toContain("/login");
});
});
// ---------------------------------------------------------------------------
// JSON API (Authorization-less) — same contract, but these live under
// /api/user/keys where the redirect target is still /login.
// ---------------------------------------------------------------------------
describe("ssh keys — /api/user/keys auth guard", () => {
it("GET /api/user/keys without a session → redirect to /login (302)", async () => {
const res = await app.request("/api/user/keys");
expect(res.status).toBe(302);
expect(res.headers.get("location") || "").toContain("/login");
});
it("POST /api/user/keys without a session → redirect to /login (302)", async () => {
const res = await app.request("/api/user/keys", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
title: "laptop",
public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI alice",
}),
});
expect(res.status).toBe(302);
expect(res.headers.get("location") || "").toContain("/login");
});
it("DELETE /api/user/keys/:id without a session → redirect to /login (302)", async () => {
const res = await app.request(
"/api/user/keys/00000000-0000-0000-0000-000000000000",
{ method: "DELETE" }
);
expect(res.status).toBe(302);
expect(res.headers.get("location") || "").toContain("/login");
});
it("POST /api/user/keys rejects an invalid PAT bearer with 401 JSON", async () => {
// `requireAuth` must 401 for Bearer tokens (JSON), NOT redirect.
const res = await app.request("/api/user/keys", {
method: "POST",
headers: {
"content-type": "application/json",
authorization: "Bearer glc_notarealtoken1234567890",
},
body: JSON.stringify({
title: "ci",
public_key: "ssh-ed25519 AAAA",
}),
});
if (HAS_DB) {
expect(res.status).toBe(401);
const body = await res.json().catch(() => null);
expect(body && body.error).toBeTruthy();
} else {
// Without a DB the PAT loader catches and returns null; requireAuth
// then returns 401 JSON from its invalid-bearer branch.
expect(res.status).toBe(401);
}
});
});
|