@urateam/cli 0.1.40 → 0.1.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/__tests__/env-file.test.d.ts +2 -0
  2. package/dist/__tests__/env-file.test.d.ts.map +1 -0
  3. package/dist/__tests__/env-file.test.js +89 -0
  4. package/dist/__tests__/env-file.test.js.map +1 -0
  5. package/dist/__tests__/linear-oauth.test.d.ts +2 -0
  6. package/dist/__tests__/linear-oauth.test.d.ts.map +1 -0
  7. package/dist/__tests__/linear-oauth.test.js +162 -0
  8. package/dist/__tests__/linear-oauth.test.js.map +1 -0
  9. package/dist/__tests__/oauth-state.test.d.ts +2 -0
  10. package/dist/__tests__/oauth-state.test.d.ts.map +1 -0
  11. package/dist/__tests__/oauth-state.test.js +40 -0
  12. package/dist/__tests__/oauth-state.test.js.map +1 -0
  13. package/dist/__tests__/self-auth-linear.test.d.ts +2 -0
  14. package/dist/__tests__/self-auth-linear.test.d.ts.map +1 -0
  15. package/dist/__tests__/self-auth-linear.test.js +92 -0
  16. package/dist/__tests__/self-auth-linear.test.js.map +1 -0
  17. package/dist/__tests__/service-unit.test.d.ts +2 -0
  18. package/dist/__tests__/service-unit.test.d.ts.map +1 -0
  19. package/dist/__tests__/service-unit.test.js +65 -0
  20. package/dist/__tests__/service-unit.test.js.map +1 -0
  21. package/dist/__tests__/service.test.d.ts +2 -0
  22. package/dist/__tests__/service.test.d.ts.map +1 -0
  23. package/dist/__tests__/service.test.js +193 -0
  24. package/dist/__tests__/service.test.js.map +1 -0
  25. package/dist/commands/self-auth-linear.d.ts +22 -0
  26. package/dist/commands/self-auth-linear.d.ts.map +1 -0
  27. package/dist/commands/self-auth-linear.js +100 -0
  28. package/dist/commands/self-auth-linear.js.map +1 -0
  29. package/dist/commands/service.d.ts +13 -0
  30. package/dist/commands/service.d.ts.map +1 -0
  31. package/dist/commands/service.js +164 -0
  32. package/dist/commands/service.js.map +1 -0
  33. package/dist/index.js +4 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/env-file.d.ts +3 -0
  36. package/dist/lib/env-file.d.ts.map +1 -0
  37. package/dist/lib/env-file.js +69 -0
  38. package/dist/lib/env-file.js.map +1 -0
  39. package/dist/lib/linear-oauth-deps.d.ts +14 -0
  40. package/dist/lib/linear-oauth-deps.d.ts.map +1 -0
  41. package/dist/lib/linear-oauth-deps.js +54 -0
  42. package/dist/lib/linear-oauth-deps.js.map +1 -0
  43. package/dist/lib/linear-oauth.d.ts +46 -0
  44. package/dist/lib/linear-oauth.d.ts.map +1 -0
  45. package/dist/lib/linear-oauth.js +154 -0
  46. package/dist/lib/linear-oauth.js.map +1 -0
  47. package/dist/lib/oauth-state.d.ts +9 -0
  48. package/dist/lib/oauth-state.d.ts.map +1 -0
  49. package/dist/lib/oauth-state.js +43 -0
  50. package/dist/lib/oauth-state.js.map +1 -0
  51. package/dist/lib/service-unit.d.ts +25 -0
  52. package/dist/lib/service-unit.d.ts.map +1 -0
  53. package/dist/lib/service-unit.js +67 -0
  54. package/dist/lib/service-unit.js.map +1 -0
  55. package/package.json +3 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-file.js","sourceRoot":"","sources":["../../src/lib/env-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { LinearTokenResponse } from "./linear-oauth.js";
2
+ export declare function defaultBrowserOpen(url: string): Promise<void>;
3
+ export declare function defaultFetchTokenEndpoint(body: {
4
+ code: string;
5
+ client_id: string;
6
+ client_secret: string;
7
+ redirect_uri: string;
8
+ grant_type: "authorization_code";
9
+ }): Promise<LinearTokenResponse>;
10
+ export declare function defaultFetchViewer(accessToken: string): Promise<{
11
+ workspaceId: string;
12
+ workspaceName?: string;
13
+ }>;
14
+ //# sourceMappingURL=linear-oauth-deps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-oauth-deps.d.ts","sourceRoot":"","sources":["../../src/lib/linear-oauth-deps.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAI7D,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASnE;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,oBAAoB,CAAC;CAClC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAa/B;AAED,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAqB1D"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Default real-world implementations of the LinearOAuthDeps callbacks.
3
+ * Split into its own module so tests can mock the entire shim without
4
+ * pulling in node:child_process and friends.
5
+ */
6
+ import { execFile } from "node:child_process";
7
+ import { promisify } from "node:util";
8
+ const execFileP = promisify(execFile);
9
+ export async function defaultBrowserOpen(url) {
10
+ // macOS: `open`, Linux: `xdg-open`. If neither resolves, print the URL.
11
+ const cmd = process.platform === "darwin" ? "open" : "xdg-open";
12
+ try {
13
+ await execFileP(cmd, [url]);
14
+ }
15
+ catch {
16
+ console.log("Open this URL in your browser:");
17
+ console.log(` ${url}`);
18
+ }
19
+ }
20
+ export async function defaultFetchTokenEndpoint(body) {
21
+ const params = new URLSearchParams();
22
+ for (const [k, v] of Object.entries(body))
23
+ params.set(k, v);
24
+ const res = await fetch("https://api.linear.app/oauth/token", {
25
+ method: "POST",
26
+ headers: { "content-type": "application/x-www-form-urlencoded" },
27
+ body: params.toString(),
28
+ });
29
+ if (!res.ok) {
30
+ const errText = await res.text().catch(() => res.statusText);
31
+ throw new Error(`${res.status}: ${errText.slice(0, 200)}`);
32
+ }
33
+ return (await res.json());
34
+ }
35
+ export async function defaultFetchViewer(accessToken) {
36
+ const query = "query { organization { id name } }";
37
+ const res = await fetch("https://api.linear.app/graphql", {
38
+ method: "POST",
39
+ headers: {
40
+ "content-type": "application/json",
41
+ authorization: `Bearer ${accessToken}`,
42
+ },
43
+ body: JSON.stringify({ query }),
44
+ });
45
+ if (!res.ok) {
46
+ throw new Error(`Failed to fetch workspace metadata: ${res.status} ${res.statusText}`);
47
+ }
48
+ const json = (await res.json());
49
+ const org = json?.data?.organization;
50
+ if (!org?.id)
51
+ throw new Error("Linear returned no organization id");
52
+ return { workspaceId: org.id, workspaceName: org.name };
53
+ }
54
+ //# sourceMappingURL=linear-oauth-deps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-oauth-deps.js","sourceRoot":"","sources":["../../src/lib/linear-oauth-deps.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEtC,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAW;IAClD,wEAAwE;IACxE,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,IAM/C;IACC,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,oCAAoC,EAAE;QAC5D,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE;KACxB,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAwB,CAAC;AACnD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAmB;IAEnB,MAAM,KAAK,GAAG,oCAAoC,CAAC;IACnD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,gCAAgC,EAAE;QACxD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,WAAW,EAAE;SACvC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;KAChC,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,uCAAuC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAE7B,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC;IACrC,IAAI,CAAC,GAAG,EAAE,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACpE,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,46 @@
1
+ export interface LinearOAuthDeps {
2
+ clientId: string;
3
+ clientSecret: string;
4
+ scope: string;
5
+ /** Total time to wait for the operator to authorize, in milliseconds. */
6
+ timeoutMs: number;
7
+ /**
8
+ * Port to bind the local callback server to. Fixed because Linear requires
9
+ * the redirect URI registered in the OAuth app settings to match the URI
10
+ * sent in the authorize request EXACTLY (host + port + path), so a
11
+ * random port would force the operator to re-register every time.
12
+ * Pass `0` to bind a random free port (test-only — production code must
13
+ * pass a stable value).
14
+ */
15
+ port: number;
16
+ /** Opens the authorize URL in the operator's browser. Pure-test override. */
17
+ openBrowser: (url: string) => Promise<void>;
18
+ /** Exchanges the code for an access token. */
19
+ fetchTokenEndpoint: (body: {
20
+ code: string;
21
+ client_id: string;
22
+ client_secret: string;
23
+ redirect_uri: string;
24
+ grant_type: "authorization_code";
25
+ }) => Promise<LinearTokenResponse>;
26
+ /** Fetches workspace metadata for the audit event. */
27
+ fetchViewer: (accessToken: string) => Promise<{
28
+ workspaceId: string;
29
+ workspaceName?: string;
30
+ }>;
31
+ }
32
+ export interface LinearTokenResponse {
33
+ access_token: string;
34
+ token_type: string;
35
+ expires_in: number;
36
+ scope: string;
37
+ }
38
+ export interface LinearOAuthResult {
39
+ accessToken: string;
40
+ workspaceId: string;
41
+ workspaceName?: string;
42
+ scope: string;
43
+ expiresInSeconds: number;
44
+ }
45
+ export declare function runLinearOAuth(deps: LinearOAuthDeps): Promise<LinearOAuthResult>;
46
+ //# sourceMappingURL=linear-oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-oauth.d.ts","sourceRoot":"","sources":["../../src/lib/linear-oauth.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,8CAA8C;IAC9C,kBAAkB,EAAE,CAAC,IAAI,EAAE;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,oBAAoB,CAAC;KAClC,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,sDAAsD;IACtD,WAAW,EAAE,CACX,WAAW,EAAE,MAAM,KAChB,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAkBD,wBAAsB,cAAc,CAClC,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,iBAAiB,CAAC,CA6J5B"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Linear OAuth 2.0 authorization-code flow, runnable headlessly with
3
+ * dependency-injected browser-open and HTTP transport for tests.
4
+ *
5
+ * High-level:
6
+ * 1. Bind an ephemeral 127.0.0.1 server (random free port).
7
+ * 2. Redirect the operator's browser to Linear with the loopback callback.
8
+ * 3. Verify the HMAC-signed state on the callback.
9
+ * 4. Exchange the code for an access token.
10
+ * 5. Look up workspace metadata (for the audit event).
11
+ * 6. Shut the server down.
12
+ *
13
+ * Token handling: the access token never crosses console.log, never lands
14
+ * in the success-page HTML, and never appears in the audit event payload.
15
+ */
16
+ import { createServer } from "node:http";
17
+ import { randomBytes } from "node:crypto";
18
+ import { newNonce, signState, verifyState } from "./oauth-state.js";
19
+ const SUCCESS_HTML = `<!doctype html>
20
+ <html><head><meta charset="utf-8"><title>urateam OAuth</title></head>
21
+ <body style="font-family: -apple-system, sans-serif; max-width: 520px; margin: 80px auto; line-height: 1.5;">
22
+ <h1>Authorized</h1>
23
+ <p>You can close this tab. Return to your terminal to continue.</p>
24
+ </body></html>`;
25
+ const ERROR_HTML = (msg) => `<!doctype html>
26
+ <html><head><meta charset="utf-8"><title>urateam OAuth</title></head>
27
+ <body style="font-family: -apple-system, sans-serif; max-width: 520px; margin: 80px auto; line-height: 1.5;">
28
+ <h1>Authorization failed</h1>
29
+ <p>${msg}</p>
30
+ </body></html>`;
31
+ const AUTHORIZE_URL = "https://linear.app/oauth/authorize";
32
+ export async function runLinearOAuth(deps) {
33
+ const stateSecret = randomBytes(32).toString("hex");
34
+ const nonce = newNonce();
35
+ const state = signState(stateSecret, nonce);
36
+ return await new Promise((resolve, reject) => {
37
+ let resolved = false;
38
+ let server = null;
39
+ const timeout = setTimeout(() => {
40
+ if (resolved)
41
+ return;
42
+ resolved = true;
43
+ server?.close();
44
+ reject(new Error(`ura self-auth-linear: timed out waiting for the OAuth callback (${deps.timeoutMs}ms)`));
45
+ }, deps.timeoutMs);
46
+ const finalize = (ok, err, e) => {
47
+ if (resolved)
48
+ return;
49
+ resolved = true;
50
+ clearTimeout(timeout);
51
+ server?.close();
52
+ if (e && err)
53
+ err(e);
54
+ else
55
+ ok();
56
+ };
57
+ server = createServer(async (req, res) => {
58
+ try {
59
+ const url = new URL(req.url ?? "/", `http://127.0.0.1`);
60
+ if (url.pathname !== "/callback") {
61
+ res.writeHead(404).end();
62
+ return;
63
+ }
64
+ const code = url.searchParams.get("code");
65
+ const incomingState = url.searchParams.get("state") ?? "";
66
+ const verified = verifyState(stateSecret, incomingState);
67
+ if (!verified || verified !== nonce) {
68
+ res.writeHead(400, { "content-type": "text/html" });
69
+ res.end(ERROR_HTML("State mismatch — possible CSRF; abort."));
70
+ finalize(() => { }, (e) => reject(e), new Error("ura self-auth-linear: state mismatch — aborting"));
71
+ return;
72
+ }
73
+ if (!code) {
74
+ res.writeHead(400, { "content-type": "text/html" });
75
+ res.end(ERROR_HTML("Missing 'code' parameter from Linear."));
76
+ finalize(() => { }, (e) => reject(e), new Error("ura self-auth-linear: missing code in callback"));
77
+ return;
78
+ }
79
+ const port = server.address().port;
80
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
81
+ const token = await deps.fetchTokenEndpoint({
82
+ code,
83
+ client_id: deps.clientId,
84
+ client_secret: deps.clientSecret,
85
+ redirect_uri: redirectUri,
86
+ grant_type: "authorization_code",
87
+ });
88
+ // Render the success page IMMEDIATELY after token exchange succeeds.
89
+ // The operator's view of "Authorized" must not depend on the
90
+ // subsequent viewer fetch, which is only used for the audit event's
91
+ // display name. If `fetchViewer` fails (Linear GraphQL hiccup), we
92
+ // still resolve with the token — falling back to an "unknown"
93
+ // workspace placeholder.
94
+ res.writeHead(200, { "content-type": "text/html" });
95
+ res.end(SUCCESS_HTML);
96
+ let viewer;
97
+ try {
98
+ viewer = await deps.fetchViewer(token.access_token);
99
+ }
100
+ catch {
101
+ viewer = { workspaceId: "unknown", workspaceName: undefined };
102
+ }
103
+ if (!resolved) {
104
+ resolved = true;
105
+ clearTimeout(timeout);
106
+ server?.close();
107
+ resolve({
108
+ accessToken: token.access_token,
109
+ workspaceId: viewer.workspaceId,
110
+ workspaceName: viewer.workspaceName,
111
+ scope: token.scope,
112
+ expiresInSeconds: token.expires_in,
113
+ });
114
+ }
115
+ }
116
+ catch (err) {
117
+ const message = err instanceof Error ? err.message : String(err);
118
+ try {
119
+ res.writeHead(500, { "content-type": "text/html" });
120
+ res.end(ERROR_HTML("Token exchange failed; check the terminal."));
121
+ }
122
+ catch {
123
+ // response may already be sent
124
+ }
125
+ finalize(() => { }, (e) => reject(e), new Error(`ura self-auth-linear: ${message}`));
126
+ }
127
+ });
128
+ server.on("error", (err) => {
129
+ if (err.code === "EADDRINUSE") {
130
+ finalize(() => { }, (e) => reject(e), new Error(`ura self-auth-linear: port ${deps.port} is already in use. ` +
131
+ `Pass --port <other-port> and register the matching redirect URI in your Linear OAuth app.`));
132
+ return;
133
+ }
134
+ finalize(() => { }, (e) => reject(e), err instanceof Error ? err : new Error(String(err)));
135
+ });
136
+ server.listen(deps.port, "127.0.0.1", async () => {
137
+ try {
138
+ const port = server.address().port;
139
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
140
+ const authUrl = new URL(AUTHORIZE_URL);
141
+ authUrl.searchParams.set("client_id", deps.clientId);
142
+ authUrl.searchParams.set("redirect_uri", redirectUri);
143
+ authUrl.searchParams.set("response_type", "code");
144
+ authUrl.searchParams.set("scope", deps.scope);
145
+ authUrl.searchParams.set("state", state);
146
+ await deps.openBrowser(authUrl.toString());
147
+ }
148
+ catch (err) {
149
+ finalize(() => { }, (e) => reject(e), err instanceof Error ? err : new Error(String(err)));
150
+ }
151
+ });
152
+ });
153
+ }
154
+ //# sourceMappingURL=linear-oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-oauth.js","sourceRoot":"","sources":["../../src/lib/linear-oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare function newNonce(): string;
2
+ export declare function signState(secret: string, nonce: string): string;
3
+ /**
4
+ * Returns the verified nonce when `state` is valid; `null` otherwise.
5
+ * Constant-time signature comparison so attacker timing observations don't
6
+ * leak the expected signature byte-by-byte.
7
+ */
8
+ export declare function verifyState(secret: string, state: string): string | null;
9
+ //# sourceMappingURL=oauth-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-state.d.ts","sourceRoot":"","sources":["../../src/lib/oauth-state.ts"],"names":[],"mappings":"AAUA,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAexE"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * HMAC-signed OAuth `state` parameter helpers.
3
+ *
4
+ * The OAuth provider echoes `state` back on the callback; verifying the HMAC
5
+ * before trusting the callback's `code` defends against open-redirect / CSRF
6
+ * attacks. Format: `<nonce>.<hmac-sha256-hex>`. The secret is per-invocation
7
+ * and never persisted — sign + verify happen in the same process.
8
+ */
9
+ import { createHmac, timingSafeEqual, randomBytes } from "node:crypto";
10
+ export function newNonce() {
11
+ return randomBytes(16).toString("hex");
12
+ }
13
+ export function signState(secret, nonce) {
14
+ const sig = createHmac("sha256", secret).update(nonce).digest("hex");
15
+ return `${nonce}.${sig}`;
16
+ }
17
+ /**
18
+ * Returns the verified nonce when `state` is valid; `null` otherwise.
19
+ * Constant-time signature comparison so attacker timing observations don't
20
+ * leak the expected signature byte-by-byte.
21
+ */
22
+ export function verifyState(secret, state) {
23
+ if (!state)
24
+ return null;
25
+ const parts = state.split(".");
26
+ if (parts.length !== 2)
27
+ return null;
28
+ const [nonce, providedSig] = parts;
29
+ if (!nonce || !providedSig)
30
+ return null;
31
+ const expectedSig = createHmac("sha256", secret).update(nonce).digest("hex");
32
+ const a = Buffer.from(providedSig, "hex");
33
+ const b = Buffer.from(expectedSig, "hex");
34
+ if (a.length !== b.length)
35
+ return null;
36
+ try {
37
+ return timingSafeEqual(a, b) ? nonce : null;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ //# sourceMappingURL=oauth-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-state.js","sourceRoot":"","sources":["../../src/lib/oauth-state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEvE,MAAM,UAAU,QAAQ;IACtB,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,KAAa;IACrD,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrE,OAAO,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,KAAa;IACvD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC;IACnC,IAAI,CAAC,KAAK,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7E,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAC1C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAC1C,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,CAAC;QACH,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Pure unit-file generators for `ura service install`.
3
+ *
4
+ * Both generators are I/O-free string functions so they can be unit-tested
5
+ * without touching the filesystem and so `--dry-run` can print the would-be
6
+ * unit content without mutating anything. The shape mirrors what
7
+ * `deploy/USER_LEVEL_INSTALL.md` previously documented as copy-paste blocks.
8
+ */
9
+ export interface ServiceUnitInput {
10
+ /** Absolute path to the `ura` binary (e.g. `/usr/local/bin/ura`). */
11
+ binaryPath: string;
12
+ /** Resolved `$URATEAM_HOME` — propagated into the service environment. */
13
+ urateamHome: string;
14
+ /** Path to the `.env` file the daemon should read at startup. */
15
+ envFilePath: string;
16
+ /** Stdout log path (launchd `StandardOutPath` / systemd `StandardOutput`). */
17
+ stdoutPath: string;
18
+ /** Stderr log path (launchd `StandardErrorPath` / systemd `StandardError`). */
19
+ stderrPath: string;
20
+ }
21
+ export declare function renderLaunchdPlist(opts: ServiceUnitInput): string;
22
+ export declare function renderSystemdUserUnit(opts: ServiceUnitInput): string;
23
+ export declare const SERVICE_LABEL = "com.urateam.daemon";
24
+ export declare const SYSTEMD_UNIT_BASENAME = "urateam.service";
25
+ //# sourceMappingURL=service-unit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-unit.d.ts","sourceRoot":"","sources":["../../src/lib/service-unit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAgCjE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAqBpE;AAED,eAAO,MAAM,aAAa,uBAAgB,CAAC;AAC3C,eAAO,MAAM,qBAAqB,oBAAoB,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Pure unit-file generators for `ura service install`.
3
+ *
4
+ * Both generators are I/O-free string functions so they can be unit-tested
5
+ * without touching the filesystem and so `--dry-run` can print the would-be
6
+ * unit content without mutating anything. The shape mirrors what
7
+ * `deploy/USER_LEVEL_INSTALL.md` previously documented as copy-paste blocks.
8
+ */
9
+ const LAUNCHD_LABEL = "com.urateam.daemon";
10
+ export function renderLaunchdPlist(opts) {
11
+ return [
12
+ `<?xml version="1.0" encoding="UTF-8"?>`,
13
+ `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">`,
14
+ `<plist version="1.0">`,
15
+ `<dict>`,
16
+ ` <key>Label</key>`,
17
+ ` <string>${LAUNCHD_LABEL}</string>`,
18
+ ` <key>ProgramArguments</key>`,
19
+ ` <array>`,
20
+ ` <string>${opts.binaryPath}</string>`,
21
+ ` <string>start</string>`,
22
+ ` </array>`,
23
+ ` <key>WorkingDirectory</key>`,
24
+ ` <string>${opts.urateamHome}</string>`,
25
+ ` <key>EnvironmentVariables</key>`,
26
+ ` <dict>`,
27
+ ` <key>URATEAM_HOME</key>`,
28
+ ` <string>${opts.urateamHome}</string>`,
29
+ ` </dict>`,
30
+ ` <key>RunAtLoad</key>`,
31
+ ` <true/>`,
32
+ ` <key>KeepAlive</key>`,
33
+ ` <true/>`,
34
+ ` <key>StandardOutPath</key>`,
35
+ ` <string>${opts.stdoutPath}</string>`,
36
+ ` <key>StandardErrorPath</key>`,
37
+ ` <string>${opts.stderrPath}</string>`,
38
+ `</dict>`,
39
+ `</plist>`,
40
+ ``,
41
+ ].join("\n");
42
+ }
43
+ export function renderSystemdUserUnit(opts) {
44
+ return [
45
+ `[Unit]`,
46
+ `Description=urateam user-level daemon`,
47
+ `After=network.target`,
48
+ ``,
49
+ `[Service]`,
50
+ `Type=simple`,
51
+ `WorkingDirectory=${opts.urateamHome}`,
52
+ `EnvironmentFile=${opts.envFilePath}`,
53
+ `Environment=URATEAM_HOME=${opts.urateamHome}`,
54
+ `ExecStart=${opts.binaryPath} start`,
55
+ `StandardOutput=append:${opts.stdoutPath}`,
56
+ `StandardError=append:${opts.stderrPath}`,
57
+ `Restart=always`,
58
+ `RestartSec=5`,
59
+ ``,
60
+ `[Install]`,
61
+ `WantedBy=default.target`,
62
+ ``,
63
+ ].join("\n");
64
+ }
65
+ export const SERVICE_LABEL = LAUNCHD_LABEL;
66
+ export const SYSTEMD_UNIT_BASENAME = "urateam.service";
67
+ //# sourceMappingURL=service-unit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-unit.js","sourceRoot":"","sources":["../../src/lib/service-unit.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAE3C,MAAM,UAAU,kBAAkB,CAAC,IAAsB;IACvD,OAAO;QACL,wCAAwC;QACxC,wGAAwG;QACxG,uBAAuB;QACvB,QAAQ;QACR,oBAAoB;QACpB,aAAa,aAAa,WAAW;QACrC,+BAA+B;QAC/B,WAAW;QACX,eAAe,IAAI,CAAC,UAAU,WAAW;QACzC,4BAA4B;QAC5B,YAAY;QACZ,+BAA+B;QAC/B,aAAa,IAAI,CAAC,WAAW,WAAW;QACxC,mCAAmC;QACnC,UAAU;QACV,6BAA6B;QAC7B,eAAe,IAAI,CAAC,WAAW,WAAW;QAC1C,WAAW;QACX,wBAAwB;QACxB,WAAW;QACX,wBAAwB;QACxB,WAAW;QACX,8BAA8B;QAC9B,aAAa,IAAI,CAAC,UAAU,WAAW;QACvC,gCAAgC;QAChC,aAAa,IAAI,CAAC,UAAU,WAAW;QACvC,SAAS;QACT,UAAU;QACV,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAsB;IAC1D,OAAO;QACL,QAAQ;QACR,uCAAuC;QACvC,sBAAsB;QACtB,EAAE;QACF,WAAW;QACX,aAAa;QACb,oBAAoB,IAAI,CAAC,WAAW,EAAE;QACtC,mBAAmB,IAAI,CAAC,WAAW,EAAE;QACrC,4BAA4B,IAAI,CAAC,WAAW,EAAE;QAC9C,aAAa,IAAI,CAAC,UAAU,QAAQ;QACpC,yBAAyB,IAAI,CAAC,UAAU,EAAE;QAC1C,wBAAwB,IAAI,CAAC,UAAU,EAAE;QACzC,gBAAgB;QAChB,cAAc;QACd,EAAE;QACF,WAAW;QACX,yBAAyB;QACzB,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAC3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,iBAAiB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@urateam/cli",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
4
4
  "license": "BUSL-1.1",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,8 +23,8 @@
23
23
  "commander": "^13.1.0",
24
24
  "postgres": "^3.4.0",
25
25
  "zod": "^4.3.6",
26
- "@urateam/core": "0.1.38",
27
- "@urateam/dashboard": "0.1.38"
26
+ "@urateam/core": "0.1.40",
27
+ "@urateam/dashboard": "0.1.40"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/better-sqlite3": "^7.6.0",