Skip to content

Commit 2aa197c

Browse files
committed
Make AuthAction an enum and enforce it's handling
1 parent 12c7c98 commit 2aa197c

File tree

5 files changed

+42
-26
lines changed

5 files changed

+42
-26
lines changed

.eslintrc.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
"prefer": "type-imports",
4747
"fixStyle": "inline-type-imports"
4848
}
49+
],
50+
"@typescript-eslint/switch-exhaustiveness-check": [
51+
"error",
52+
{ "considerDefaultExhaustiveForUnions": true }
4953
]
5054
}
5155
},

src/core/secretsManager.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ const SESSION_TOKEN_KEY = "sessionToken";
44

55
const LOGIN_STATE_KEY = "loginState";
66

7-
type AuthAction = "login" | "logout";
7+
export enum AuthAction {
8+
LOGIN,
9+
LOGOUT,
10+
INVALID,
11+
}
812

913
export class SecretsManager {
1014
constructor(private readonly secrets: SecretStorage) {}
@@ -38,7 +42,9 @@ export class SecretsManager {
3842
* Uses the secrets storage onDidChange event as a cross-window communication mechanism.
3943
* Appends a timestamp to ensure the value always changes, guaranteeing the event fires.
4044
*/
41-
public async triggerLoginStateChange(action: AuthAction): Promise<void> {
45+
public async triggerLoginStateChange(
46+
action: "login" | "logout",
47+
): Promise<void> {
4248
const date = new Date().toISOString();
4349
await this.secrets.store(LOGIN_STATE_KEY, `${action}-${date}`);
4450
}
@@ -48,18 +54,18 @@ export class SecretsManager {
4854
* The secrets storage onDidChange event fires across all windows, enabling cross-window sync.
4955
*/
5056
public onDidChangeLoginState(
51-
listener: (state?: AuthAction) => Promise<void>,
57+
listener: (state: AuthAction) => Promise<void>,
5258
): Disposable {
5359
return this.secrets.onDidChange(async (e) => {
5460
if (e.key === LOGIN_STATE_KEY) {
5561
const state = await this.secrets.get(LOGIN_STATE_KEY);
5662
if (state?.startsWith("login")) {
57-
listener("login");
63+
listener(AuthAction.LOGIN);
5864
} else if (state?.startsWith("logout")) {
59-
listener("logout");
65+
listener(AuthAction.LOGOUT);
6066
} else {
6167
// Secret was deleted or is invalid
62-
listener(undefined);
68+
listener(AuthAction.INVALID);
6369
}
6470
}
6571
});

src/error.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export class CertificateError extends Error {
6464
return new CertificateError(err.message, X509_ERR.UNTRUSTED_LEAF);
6565
case X509_ERR_CODE.SELF_SIGNED_CERT_IN_CHAIN:
6666
return new CertificateError(err.message, X509_ERR.UNTRUSTED_CHAIN);
67+
case undefined:
68+
break;
6769
}
6870
}
6971
return err;
@@ -154,6 +156,7 @@ export class CertificateError extends Error {
154156
);
155157
switch (val) {
156158
case CertificateError.ActionOK:
159+
case undefined:
157160
return;
158161
case CertificateError.ActionAllowInsecure:
159162
await this.allowInsecure();

src/extension.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { CoderApi } from "./api/coderApi";
1010
import { needToken } from "./api/utils";
1111
import { Commands } from "./commands";
1212
import { ServiceContainer } from "./core/container";
13+
import { AuthAction } from "./core/secretsManager";
1314
import { CertificateError, getErrorDetail } from "./error";
1415
import { Remote } from "./remote/remote";
1516
import { toSafeHost } from "./util";
@@ -333,19 +334,21 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
333334

334335
ctx.subscriptions.push(
335336
secretsManager.onDidChangeLoginState(async (state) => {
336-
if (state === undefined) {
337-
return;
338-
}
339-
340-
if (state === "login") {
341-
const token = await secretsManager.getSessionToken();
342-
const url = mementoManager.getUrl();
343-
// Should login the user directly if the URL+Token are valid
344-
await commands.login({ url, token });
345-
// Resolve any pending login detection promises
346-
remote.resolveLoginDetected();
347-
} else {
348-
await commands.forceLogout();
337+
switch (state) {
338+
case AuthAction.LOGIN: {
339+
const token = await secretsManager.getSessionToken();
340+
const url = mementoManager.getUrl();
341+
// Should login the user directly if the URL+Token are valid
342+
await commands.login({ url, token });
343+
// Resolve any pending login detection promises
344+
remote.resolveLoginDetected();
345+
break;
346+
}
347+
case AuthAction.LOGOUT:
348+
await commands.forceLogout();
349+
break;
350+
case AuthAction.INVALID:
351+
break;
349352
}
350353
}),
351354
);

test/unit/core/secretsManager.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { beforeEach, describe, expect, it, vi } from "vitest";
22

3-
import { SecretsManager } from "@/core/secretsManager";
3+
import { AuthAction, SecretsManager } from "@/core/secretsManager";
44

55
import { InMemorySecretStorage } from "../../mocks/testHelpers";
66

@@ -42,30 +42,30 @@ describe("SecretsManager", () => {
4242

4343
describe("login state", () => {
4444
it("should trigger login events", async () => {
45-
const events: Array<string | undefined> = [];
45+
const events: Array<AuthAction> = [];
4646
secretsManager.onDidChangeLoginState((state) => {
4747
events.push(state);
4848
return Promise.resolve();
4949
});
5050

5151
await secretsManager.triggerLoginStateChange("login");
52-
expect(events).toEqual(["login"]);
52+
expect(events).toEqual([AuthAction.LOGIN]);
5353
});
5454

5555
it("should trigger logout events", async () => {
56-
const events: Array<string | undefined> = [];
56+
const events: Array<AuthAction> = [];
5757
secretsManager.onDidChangeLoginState((state) => {
5858
events.push(state);
5959
return Promise.resolve();
6060
});
6161

6262
await secretsManager.triggerLoginStateChange("logout");
63-
expect(events).toEqual(["logout"]);
63+
expect(events).toEqual([AuthAction.LOGOUT]);
6464
});
6565

6666
it("should fire same event twice in a row", async () => {
6767
vi.useFakeTimers();
68-
const events: Array<string | undefined> = [];
68+
const events: Array<AuthAction> = [];
6969
secretsManager.onDidChangeLoginState((state) => {
7070
events.push(state);
7171
return Promise.resolve();
@@ -75,7 +75,7 @@ describe("SecretsManager", () => {
7575
vi.advanceTimersByTime(5);
7676
await secretsManager.triggerLoginStateChange("login");
7777

78-
expect(events).toEqual(["login", "login"]);
78+
expect(events).toEqual([AuthAction.LOGIN, AuthAction.LOGIN]);
7979
vi.useRealTimers();
8080
});
8181
});

0 commit comments

Comments
 (0)