Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
lint:
uses: listee-dev/listee-ci/.github/workflows/lint.yml@main

typecheck:
uses: listee-dev/listee-ci/.github/workflows/typecheck.yml@main

test:
uses: listee-dev/listee-ci/.github/workflows/test.yml@main
16 changes: 12 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"test": "bun test"
},
"dependencies": {
"@listee/auth": "link:@listee/auth",
"@listee/types": "link:@listee/types",
"@listee/auth": "^0.2.3",
"@listee/types": "^0.2.3",
"@napi-rs/keyring": "^1.2.0",
"commander": "^12.1.0",
"dotenv": "^16.4.5"
Expand Down
24 changes: 19 additions & 5 deletions src/commands/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,19 @@ const startLoopbackServer = async (): Promise<LoopbackServer> => {
let settled = false;

const server = createServer((req, res) => {
const finish = (status: number, body: string, contentType = "text/html"): void => {
const finish = (
status: number,
body: string,
contentType = "text/html",
): void => {
res.writeHead(status, { "Content-Type": contentType });
res.end(body);
};

const respondWithJson = (status: number, payload: { title: string; message: string }): void => {
const respondWithJson = (
status: number,
payload: { title: string; message: string },
): void => {
finish(status, JSON.stringify(payload), "application/json");
};

Expand Down Expand Up @@ -174,7 +181,11 @@ const startLoopbackServer = async (): Promise<LoopbackServer> => {
});

const address = server.address();
if (address === null || typeof address !== "object" || address.port === undefined) {
if (
address === null ||
typeof address !== "object" ||
address.port === undefined
) {
server.close();
throw new Error("Failed to determine loopback server port.");
}
Expand All @@ -200,7 +211,8 @@ const startLoopbackServer = async (): Promise<LoopbackServer> => {

return {
redirectUrl: `http://${LOOPBACK_HOST}:${address.port}/callback`,
waitForConfirmation: () => waitForConfirmation.finally(() => clearTimeout(timeout)),
waitForConfirmation: () =>
waitForConfirmation.finally(() => clearTimeout(timeout)),
shutdown,
};
};
Expand Down Expand Up @@ -344,7 +356,9 @@ const signupAction = async (options: EmailOption): Promise<void> => {

try {
await signup(email, password, loopback.redirectUrl);
console.log("📩 Confirmation email sent. Keep this terminal open while you click the link.");
console.log(
"📩 Confirmation email sent. Keep this terminal open while you click the link.",
);
const result = await loopback.waitForConfirmation();
console.log(`✅ Signup confirmed for ${result.account}.`);
} finally {
Expand Down
13 changes: 11 additions & 2 deletions src/services/auth-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,20 @@ describe("parseSignupFragment", () => {
};

const header = encodeSegment(JSON.stringify({ alg: "HS256", typ: "JWT" }));
const payload = encodeSegment(JSON.stringify({ email: "user@example.com" }));
const payloadSegment = (): string => {
const currentEpoch = Math.floor(Date.now() / 1000);
const payload = {
sub: "user-id",
email: "user@example.com",
exp: currentEpoch + 3600,
iat: currentEpoch,
};
return encodeSegment(JSON.stringify(payload));
};
const signature = encodeSegment("signature");
const accessToken = `${header}.${payload}.${signature}`;

it("parses tokens from a confirmation fragment", () => {
const accessToken = `${header}.${payloadSegment()}.${signature}`;
const fragment = `#access_token=${accessToken}&refresh_token=refresh123&expires_in=3600&token_type=bearer&type=signup`;
const result = parseSignupFragment(fragment);

Expand Down