@kuckit/cli-auth-module 1.0.0

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.
@@ -0,0 +1,16 @@
1
+ import { KuckitClientModuleContext } from "@kuckit/sdk-react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+
4
+ //#region src/client/pages/ActivatePage.d.ts
5
+ declare function ActivatePage(): react_jsx_runtime0.JSX.Element;
6
+ //#endregion
7
+ //#region src/client/index.d.ts
8
+ declare const kuckitClientModule: {
9
+ id: string;
10
+ displayName: string;
11
+ version: string;
12
+ register(ctx: KuckitClientModuleContext): void;
13
+ };
14
+ //#endregion
15
+ export { ActivatePage, kuckitClientModule };
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,178 @@
1
+ import { defineKuckitClientModule, useRpc } from "@kuckit/sdk-react";
2
+ import { useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+
5
+ //#region src/client/pages/ActivatePage.tsx
6
+ function formatUserCode(value) {
7
+ const cleaned = value.toUpperCase().replace(/[^A-Z0-9]/g, "");
8
+ if (cleaned.length <= 4) return cleaned;
9
+ return `${cleaned.slice(0, 4)}-${cleaned.slice(4, 8)}`;
10
+ }
11
+ function ActivatePage() {
12
+ const rpc = useRpc();
13
+ const [userCode, setUserCode] = useState("");
14
+ const [state, setState] = useState("idle");
15
+ const [message, setMessage] = useState("");
16
+ const handleChange = (e) => {
17
+ setUserCode(formatUserCode(e.target.value));
18
+ };
19
+ const handleSubmit = async (e) => {
20
+ e.preventDefault();
21
+ if (userCode.replace(/-/g, "").length !== 8) {
22
+ setState("error");
23
+ setMessage("Please enter a valid 8-character code");
24
+ return;
25
+ }
26
+ setState("loading");
27
+ setMessage("");
28
+ try {
29
+ const result = await rpc.cliDevice.approve({ userCode });
30
+ if (result.success) {
31
+ setState("success");
32
+ setMessage("Device authorized successfully! You can close this window and return to your terminal.");
33
+ } else {
34
+ setState("error");
35
+ setMessage(result.message || "Failed to authorize device. Please check the code and try again.");
36
+ }
37
+ } catch (error) {
38
+ console.error("Device activation error:", error);
39
+ if (error instanceof Error && error.message.includes("401")) {
40
+ window.location.href = "/sign-in?redirect=/cli/activate";
41
+ return;
42
+ }
43
+ setState("error");
44
+ setMessage("Network error. Please try again.");
45
+ }
46
+ };
47
+ return /* @__PURE__ */ jsx("div", {
48
+ className: "min-h-screen bg-[#0a0a0b] flex items-center justify-center p-6",
49
+ children: /* @__PURE__ */ jsx("div", {
50
+ className: "w-full max-w-md",
51
+ children: /* @__PURE__ */ jsxs("div", {
52
+ className: "bg-white/5 border border-white/10 rounded-2xl p-8",
53
+ children: [
54
+ /* @__PURE__ */ jsxs("div", {
55
+ className: "text-center mb-8",
56
+ children: [
57
+ /* @__PURE__ */ jsx("div", {
58
+ className: "w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-400 to-teal-500 flex items-center justify-center mx-auto mb-4",
59
+ children: /* @__PURE__ */ jsx("svg", {
60
+ className: "w-6 h-6 text-black",
61
+ fill: "none",
62
+ stroke: "currentColor",
63
+ viewBox: "0 0 24 24",
64
+ children: /* @__PURE__ */ jsx("path", {
65
+ strokeLinecap: "round",
66
+ strokeLinejoin: "round",
67
+ strokeWidth: 2,
68
+ d: "M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
69
+ })
70
+ })
71
+ }),
72
+ /* @__PURE__ */ jsx("h1", {
73
+ className: "text-2xl font-bold text-white mb-2",
74
+ children: "Activate CLI Access"
75
+ }),
76
+ /* @__PURE__ */ jsx("p", {
77
+ className: "text-white/60 text-sm",
78
+ children: "Enter the code displayed in your terminal to authorize CLI access"
79
+ })
80
+ ]
81
+ }),
82
+ state === "success" ? /* @__PURE__ */ jsxs("div", {
83
+ className: "text-center",
84
+ children: [
85
+ /* @__PURE__ */ jsx("div", {
86
+ className: "w-16 h-16 rounded-full bg-emerald-500/20 flex items-center justify-center mx-auto mb-4",
87
+ children: /* @__PURE__ */ jsx("svg", {
88
+ className: "w-8 h-8 text-emerald-400",
89
+ fill: "none",
90
+ stroke: "currentColor",
91
+ viewBox: "0 0 24 24",
92
+ children: /* @__PURE__ */ jsx("path", {
93
+ strokeLinecap: "round",
94
+ strokeLinejoin: "round",
95
+ strokeWidth: 2,
96
+ d: "M5 13l4 4L19 7"
97
+ })
98
+ })
99
+ }),
100
+ /* @__PURE__ */ jsx("p", {
101
+ className: "text-emerald-400 font-medium mb-2",
102
+ children: "Success!"
103
+ }),
104
+ /* @__PURE__ */ jsx("p", {
105
+ className: "text-white/60 text-sm",
106
+ children: message
107
+ })
108
+ ]
109
+ }) : /* @__PURE__ */ jsxs("form", {
110
+ onSubmit: handleSubmit,
111
+ children: [
112
+ /* @__PURE__ */ jsxs("div", {
113
+ className: "mb-6",
114
+ children: [/* @__PURE__ */ jsx("label", {
115
+ htmlFor: "userCode",
116
+ className: "block text-sm font-medium text-white/80 mb-2",
117
+ children: "Activation Code"
118
+ }), /* @__PURE__ */ jsx("input", {
119
+ id: "userCode",
120
+ type: "text",
121
+ value: userCode,
122
+ onChange: handleChange,
123
+ placeholder: "XXXX-XXXX",
124
+ maxLength: 9,
125
+ disabled: state === "loading",
126
+ className: "w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white text-center text-2xl font-mono tracking-widest placeholder:text-white/20 focus:outline-none focus:border-emerald-500/50 focus:ring-2 focus:ring-emerald-500/20 transition-all disabled:opacity-50",
127
+ autoComplete: "off",
128
+ autoFocus: true
129
+ })]
130
+ }),
131
+ state === "error" && message && /* @__PURE__ */ jsx("div", {
132
+ className: "mb-4 p-3 rounded-lg bg-red-500/10 border border-red-500/20",
133
+ children: /* @__PURE__ */ jsx("p", {
134
+ className: "text-red-400 text-sm text-center",
135
+ children: message
136
+ })
137
+ }),
138
+ /* @__PURE__ */ jsx("button", {
139
+ type: "submit",
140
+ disabled: state === "loading" || userCode.replace(/-/g, "").length !== 8,
141
+ className: "w-full px-6 py-3 rounded-xl bg-gradient-to-r from-emerald-500 to-teal-500 font-medium text-black hover:from-emerald-400 hover:to-teal-400 transition-all disabled:opacity-50 disabled:cursor-not-allowed",
142
+ children: state === "loading" ? "Authorizing..." : "Authorize Device"
143
+ })
144
+ ]
145
+ }),
146
+ /* @__PURE__ */ jsx("p", {
147
+ className: "text-white/40 text-xs text-center mt-6",
148
+ children: "This will grant CLI access to your account for 90 days"
149
+ })
150
+ ]
151
+ })
152
+ })
153
+ });
154
+ }
155
+
156
+ //#endregion
157
+ //#region src/client/index.ts
158
+ const kuckitClientModule = defineKuckitClientModule({
159
+ id: "kuckit.cli-auth",
160
+ displayName: "CLI Authentication",
161
+ version: "0.1.0",
162
+ register(ctx) {
163
+ ctx.registerComponent("ActivatePage", ActivatePage);
164
+ ctx.addRoute({
165
+ id: "cli-activate",
166
+ path: "/cli/activate",
167
+ component: ActivatePage,
168
+ meta: {
169
+ title: "Activate CLI Access",
170
+ requiresAuth: true
171
+ }
172
+ });
173
+ }
174
+ });
175
+
176
+ //#endregion
177
+ export { ActivatePage, kuckitClientModule };
178
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["error: unknown"],"sources":["../../src/client/pages/ActivatePage.tsx","../../src/client/index.ts"],"sourcesContent":["'use client'\n\nimport { useState, type FormEvent, type ChangeEvent } from 'react'\nimport { useRpc } from '@kuckit/sdk-react'\n\ntype FormState = 'idle' | 'loading' | 'success' | 'error'\n\ninterface CliDeviceRouter {\n\tcliDevice: {\n\t\tapprove: (input: { userCode: string }) => Promise<{ success: boolean; message?: string }>\n\t}\n}\n\nfunction formatUserCode(value: string): string {\n\tconst cleaned = value.toUpperCase().replace(/[^A-Z0-9]/g, '')\n\tif (cleaned.length <= 4) {\n\t\treturn cleaned\n\t}\n\treturn `${cleaned.slice(0, 4)}-${cleaned.slice(4, 8)}`\n}\n\nexport function ActivatePage() {\n\tconst rpc = useRpc<CliDeviceRouter>()\n\tconst [userCode, setUserCode] = useState('')\n\tconst [state, setState] = useState<FormState>('idle')\n\tconst [message, setMessage] = useState('')\n\n\tconst handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n\t\tconst formatted = formatUserCode(e.target.value)\n\t\tsetUserCode(formatted)\n\t}\n\n\tconst handleSubmit = async (e: FormEvent) => {\n\t\te.preventDefault()\n\n\t\tconst cleanCode = userCode.replace(/-/g, '')\n\t\tif (cleanCode.length !== 8) {\n\t\t\tsetState('error')\n\t\t\tsetMessage('Please enter a valid 8-character code')\n\t\t\treturn\n\t\t}\n\n\t\tsetState('loading')\n\t\tsetMessage('')\n\n\t\ttry {\n\t\t\tconst result = await rpc.cliDevice.approve({ userCode })\n\n\t\t\tif (result.success) {\n\t\t\t\tsetState('success')\n\t\t\t\tsetMessage(\n\t\t\t\t\t'Device authorized successfully! You can close this window and return to your terminal.'\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tsetState('error')\n\t\t\t\tsetMessage(\n\t\t\t\t\tresult.message || 'Failed to authorize device. Please check the code and try again.'\n\t\t\t\t)\n\t\t\t}\n\t\t} catch (error: unknown) {\n\t\t\tconsole.error('Device activation error:', error)\n\t\t\tif (error instanceof Error && error.message.includes('401')) {\n\t\t\t\twindow.location.href = '/sign-in?redirect=/cli/activate'\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsetState('error')\n\t\t\tsetMessage('Network error. Please try again.')\n\t\t}\n\t}\n\n\treturn (\n\t\t<div className=\"min-h-screen bg-[#0a0a0b] flex items-center justify-center p-6\">\n\t\t\t<div className=\"w-full max-w-md\">\n\t\t\t\t<div className=\"bg-white/5 border border-white/10 rounded-2xl p-8\">\n\t\t\t\t\t<div className=\"text-center mb-8\">\n\t\t\t\t\t\t<div className=\"w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-400 to-teal-500 flex items-center justify-center mx-auto mb-4\">\n\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\tclassName=\"w-6 h-6 text-black\"\n\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t\t\tstrokeWidth={2}\n\t\t\t\t\t\t\t\t\td=\"M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<h1 className=\"text-2xl font-bold text-white mb-2\">Activate CLI Access</h1>\n\t\t\t\t\t\t<p className=\"text-white/60 text-sm\">\n\t\t\t\t\t\t\tEnter the code displayed in your terminal to authorize CLI access\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t{state === 'success' ? (\n\t\t\t\t\t\t<div className=\"text-center\">\n\t\t\t\t\t\t\t<div className=\"w-16 h-16 rounded-full bg-emerald-500/20 flex items-center justify-center mx-auto mb-4\">\n\t\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\t\tclassName=\"w-8 h-8 text-emerald-400\"\n\t\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t\t\t\tstrokeWidth={2}\n\t\t\t\t\t\t\t\t\t\td=\"M5 13l4 4L19 7\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<p className=\"text-emerald-400 font-medium mb-2\">Success!</p>\n\t\t\t\t\t\t\t<p className=\"text-white/60 text-sm\">{message}</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<form onSubmit={handleSubmit}>\n\t\t\t\t\t\t\t<div className=\"mb-6\">\n\t\t\t\t\t\t\t\t<label htmlFor=\"userCode\" className=\"block text-sm font-medium text-white/80 mb-2\">\n\t\t\t\t\t\t\t\t\tActivation Code\n\t\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\tid=\"userCode\"\n\t\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\t\tvalue={userCode}\n\t\t\t\t\t\t\t\t\tonChange={handleChange}\n\t\t\t\t\t\t\t\t\tplaceholder=\"XXXX-XXXX\"\n\t\t\t\t\t\t\t\t\tmaxLength={9}\n\t\t\t\t\t\t\t\t\tdisabled={state === 'loading'}\n\t\t\t\t\t\t\t\t\tclassName=\"w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white text-center text-2xl font-mono tracking-widest placeholder:text-white/20 focus:outline-none focus:border-emerald-500/50 focus:ring-2 focus:ring-emerald-500/20 transition-all disabled:opacity-50\"\n\t\t\t\t\t\t\t\t\tautoComplete=\"off\"\n\t\t\t\t\t\t\t\t\tautoFocus\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t{state === 'error' && message && (\n\t\t\t\t\t\t\t\t<div className=\"mb-4 p-3 rounded-lg bg-red-500/10 border border-red-500/20\">\n\t\t\t\t\t\t\t\t\t<p className=\"text-red-400 text-sm text-center\">{message}</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\t\tdisabled={state === 'loading' || userCode.replace(/-/g, '').length !== 8}\n\t\t\t\t\t\t\t\tclassName=\"w-full px-6 py-3 rounded-xl bg-gradient-to-r from-emerald-500 to-teal-500 font-medium text-black hover:from-emerald-400 hover:to-teal-400 transition-all disabled:opacity-50 disabled:cursor-not-allowed\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{state === 'loading' ? 'Authorizing...' : 'Authorize Device'}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</form>\n\t\t\t\t\t)}\n\n\t\t\t\t\t<p className=\"text-white/40 text-xs text-center mt-6\">\n\t\t\t\t\t\tThis will grant CLI access to your account for 90 days\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n","import { defineKuckitClientModule, type KuckitClientModuleContext } from '@kuckit/sdk-react'\nimport { ActivatePage } from './pages/ActivatePage'\n\nexport const kuckitClientModule = defineKuckitClientModule({\n\tid: 'kuckit.cli-auth',\n\tdisplayName: 'CLI Authentication',\n\tversion: '0.1.0',\n\n\tregister(ctx: KuckitClientModuleContext) {\n\t\tctx.registerComponent('ActivatePage', ActivatePage)\n\n\t\tctx.addRoute({\n\t\t\tid: 'cli-activate',\n\t\t\tpath: '/cli/activate',\n\t\t\tcomponent: ActivatePage,\n\t\t\tmeta: {\n\t\t\t\ttitle: 'Activate CLI Access',\n\t\t\t\trequiresAuth: true,\n\t\t\t},\n\t\t})\n\t},\n})\n\nexport { ActivatePage } from './pages/ActivatePage'\n"],"mappings":";;;;;AAaA,SAAS,eAAe,OAAuB;CAC9C,MAAM,UAAU,MAAM,aAAa,CAAC,QAAQ,cAAc,GAAG;AAC7D,KAAI,QAAQ,UAAU,EACrB,QAAO;AAER,QAAO,GAAG,QAAQ,MAAM,GAAG,EAAE,CAAC,GAAG,QAAQ,MAAM,GAAG,EAAE;;AAGrD,SAAgB,eAAe;CAC9B,MAAM,MAAM,QAAyB;CACrC,MAAM,CAAC,UAAU,eAAe,SAAS,GAAG;CAC5C,MAAM,CAAC,OAAO,YAAY,SAAoB,OAAO;CACrD,MAAM,CAAC,SAAS,cAAc,SAAS,GAAG;CAE1C,MAAM,gBAAgB,MAAqC;AAE1D,cADkB,eAAe,EAAE,OAAO,MAAM,CAC1B;;CAGvB,MAAM,eAAe,OAAO,MAAiB;AAC5C,IAAE,gBAAgB;AAGlB,MADkB,SAAS,QAAQ,MAAM,GAAG,CAC9B,WAAW,GAAG;AAC3B,YAAS,QAAQ;AACjB,cAAW,wCAAwC;AACnD;;AAGD,WAAS,UAAU;AACnB,aAAW,GAAG;AAEd,MAAI;GACH,MAAM,SAAS,MAAM,IAAI,UAAU,QAAQ,EAAE,UAAU,CAAC;AAExD,OAAI,OAAO,SAAS;AACnB,aAAS,UAAU;AACnB,eACC,yFACA;UACK;AACN,aAAS,QAAQ;AACjB,eACC,OAAO,WAAW,mEAClB;;WAEMA,OAAgB;AACxB,WAAQ,MAAM,4BAA4B,MAAM;AAChD,OAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,MAAM,EAAE;AAC5D,WAAO,SAAS,OAAO;AACvB;;AAED,YAAS,QAAQ;AACjB,cAAW,mCAAmC;;;AAIhD,QACC,oBAAC;EAAI,WAAU;YACd,oBAAC;GAAI,WAAU;aACd,qBAAC;IAAI,WAAU;;KACd,qBAAC;MAAI,WAAU;;OACd,oBAAC;QAAI,WAAU;kBACd,oBAAC;SACA,WAAU;SACV,MAAK;SACL,QAAO;SACP,SAAQ;mBAER,oBAAC;UACA,eAAc;UACd,gBAAe;UACf,aAAa;UACb,GAAE;WACD;UACG;SACD;OACN,oBAAC;QAAG,WAAU;kBAAqC;SAAwB;OAC3E,oBAAC;QAAE,WAAU;kBAAwB;SAEjC;;OACC;KAEL,UAAU,YACV,qBAAC;MAAI,WAAU;;OACd,oBAAC;QAAI,WAAU;kBACd,oBAAC;SACA,WAAU;SACV,MAAK;SACL,QAAO;SACP,SAAQ;mBAER,oBAAC;UACA,eAAc;UACd,gBAAe;UACf,aAAa;UACb,GAAE;WACD;UACG;SACD;OACN,oBAAC;QAAE,WAAU;kBAAoC;SAAY;OAC7D,oBAAC;QAAE,WAAU;kBAAyB;SAAY;;OAC7C,GAEN,qBAAC;MAAK,UAAU;;OACf,qBAAC;QAAI,WAAU;mBACd,oBAAC;SAAM,SAAQ;SAAW,WAAU;mBAA+C;UAE3E,EACR,oBAAC;SACA,IAAG;SACH,MAAK;SACL,OAAO;SACP,UAAU;SACV,aAAY;SACZ,WAAW;SACX,UAAU,UAAU;SACpB,WAAU;SACV,cAAa;SACb;UACC;SACG;OAEL,UAAU,WAAW,WACrB,oBAAC;QAAI,WAAU;kBACd,oBAAC;SAAE,WAAU;mBAAoC;UAAY;SACxD;OAGP,oBAAC;QACA,MAAK;QACL,UAAU,UAAU,aAAa,SAAS,QAAQ,MAAM,GAAG,CAAC,WAAW;QACvE,WAAU;kBAET,UAAU,YAAY,mBAAmB;SAClC;;OACH;KAGR,oBAAC;MAAE,WAAU;gBAAyC;OAElD;;KACC;IACD;GACD;;;;;AC1JR,MAAa,qBAAqB,yBAAyB;CAC1D,IAAI;CACJ,aAAa;CACb,SAAS;CAET,SAAS,KAAgC;AACxC,MAAI,kBAAkB,gBAAgB,aAAa;AAEnD,MAAI,SAAS;GACZ,IAAI;GACJ,MAAM;GACN,WAAW;GACX,MAAM;IACL,OAAO;IACP,cAAc;IACd;GACD,CAAC;;CAEH,CAAC"}
@@ -0,0 +1,38 @@
1
+ import * as _kuckit_sdk0 from "@kuckit/sdk";
2
+ import "drizzle-orm/pg-core";
3
+ import "drizzle-orm";
4
+ import { NextFunction, Request, Response } from "express";
5
+
6
+ //#region src/server/use-cases/validate-cli-token.d.ts
7
+ interface ValidateCliTokenOutput {
8
+ readonly id: string;
9
+ readonly userId: string;
10
+ readonly scopes: string[];
11
+ }
12
+ //#endregion
13
+ //#region src/server/middleware/cli-auth.middleware.d.ts
14
+ interface CliAuthContext {
15
+ id: string;
16
+ userId: string;
17
+ scopes: string[];
18
+ }
19
+ declare global {
20
+ namespace Express {
21
+ interface Request {
22
+ cliAuth?: CliAuthContext;
23
+ }
24
+ }
25
+ }
26
+ type ValidateCliToken = (rawToken: string) => Promise<ValidateCliTokenOutput | null>;
27
+ interface CliAuthMiddlewareDeps {
28
+ validateCliToken: ValidateCliToken;
29
+ }
30
+ declare const makeCliAuthMiddleware: (deps: CliAuthMiddlewareDeps) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
31
+ declare const requireScope: (scope: string) => (req: Request, res: Response, next: NextFunction) => void;
32
+ //#endregion
33
+ //#region src/server/module.d.ts
34
+ type CliAuthModuleConfig = Record<string, never>;
35
+ declare const kuckitModule: _kuckit_sdk0.KuckitModuleDefinition<CliAuthModuleConfig>;
36
+ //#endregion
37
+ export { type CliAuthContext, CliAuthModuleConfig, kuckitModule, makeCliAuthMiddleware, requireScope };
38
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +1,353 @@
1
+ import { asFunction, defineKuckitModule } from "@kuckit/sdk";
2
+ import { index, integer, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core";
3
+ import { and, eq, gt, isNull, or } from "drizzle-orm";
4
+ import { createHash, randomBytes } from "crypto";
5
+ import { protectedProcedure, publicProcedure } from "@kuckit/api";
6
+ import { z } from "zod";
7
+
8
+ //#region src/server/schema/cli-auth.ts
9
+ const deviceCodeStatusEnum = pgEnum("cli_auth_device_code_status", [
10
+ "pending",
11
+ "approved",
12
+ "denied",
13
+ "expired"
14
+ ]);
15
+ const cliAuthTokenTable = pgTable("cli_auth_token", {
16
+ id: text("id").primaryKey(),
17
+ userId: text("user_id").notNull(),
18
+ tokenHash: text("token_hash").notNull().unique(),
19
+ scopes: text("scopes").array().notNull().default([]),
20
+ name: text("name"),
21
+ expiresAt: timestamp("expires_at"),
22
+ lastUsedAt: timestamp("last_used_at"),
23
+ revokedAt: timestamp("revoked_at"),
24
+ createdAt: timestamp("created_at").notNull().defaultNow()
25
+ }, (table) => ({ userIdIdx: index("cli_auth_token_user_id_idx").on(table.userId) }));
26
+ const cliAuthDeviceCodeTable = pgTable("cli_auth_device_code", {
27
+ id: text("id").primaryKey(),
28
+ deviceCode: text("device_code").notNull().unique(),
29
+ userCode: text("user_code").notNull().unique(),
30
+ scopes: text("scopes").array().notNull().default([]),
31
+ status: deviceCodeStatusEnum("status").notNull().default("pending"),
32
+ approvedUserId: text("approved_user_id"),
33
+ approvedToken: text("approved_token"),
34
+ intervalSec: integer("interval_sec").notNull().default(5),
35
+ expiresAt: timestamp("expires_at").notNull(),
36
+ createdAt: timestamp("created_at").notNull().defaultNow()
37
+ });
38
+
39
+ //#endregion
40
+ //#region src/server/repository/helpers.ts
41
+ const USER_CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
42
+ /**
43
+ * Generate a user-friendly 8-character code in XXXX-XXXX format.
44
+ * Uses crypto.randomBytes for entropy and excludes confusing characters.
45
+ */
46
+ function generateUserCode() {
47
+ const bytes = randomBytes(8);
48
+ let code = "";
49
+ for (let i = 0; i < 8; i++) {
50
+ code += USER_CODE_ALPHABET[bytes[i] % 31];
51
+ if (i === 3) code += "-";
52
+ }
53
+ return code;
54
+ }
55
+
56
+ //#endregion
57
+ //#region src/server/repository/cli-auth.repository.ts
58
+ const makeCliAuthRepository = (db) => ({
59
+ async createToken(data) {
60
+ const rawToken = `kuckit_cli_${randomBytes(32).toString("base64url")}`;
61
+ const tokenHash = createHash("sha256").update(rawToken).digest("hex");
62
+ const id = crypto.randomUUID();
63
+ const [record] = await db.insert(cliAuthTokenTable).values({
64
+ ...data,
65
+ id,
66
+ tokenHash
67
+ }).returning();
68
+ return {
69
+ token: rawToken,
70
+ record
71
+ };
72
+ },
73
+ async findTokenByHash(hash) {
74
+ const [token] = await db.select().from(cliAuthTokenTable).where(and(eq(cliAuthTokenTable.tokenHash, hash), isNull(cliAuthTokenTable.revokedAt), or(isNull(cliAuthTokenTable.expiresAt), gt(cliAuthTokenTable.expiresAt, /* @__PURE__ */ new Date())))).limit(1);
75
+ return token ?? null;
76
+ },
77
+ async updateTokenLastUsed(id) {
78
+ await db.update(cliAuthTokenTable).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(eq(cliAuthTokenTable.id, id));
79
+ },
80
+ async revokeToken(id) {
81
+ await db.update(cliAuthTokenTable).set({ revokedAt: /* @__PURE__ */ new Date() }).where(eq(cliAuthTokenTable.id, id));
82
+ },
83
+ async createDeviceCode(scopes) {
84
+ const id = crypto.randomUUID();
85
+ const deviceCode = randomBytes(32).toString("base64url");
86
+ const userCode = generateUserCode();
87
+ const [record] = await db.insert(cliAuthDeviceCodeTable).values({
88
+ id,
89
+ deviceCode,
90
+ userCode,
91
+ scopes,
92
+ expiresAt: new Date(Date.now() + 600 * 1e3)
93
+ }).returning();
94
+ return record;
95
+ },
96
+ async findDeviceCodeByCode(deviceCode) {
97
+ const [record] = await db.select().from(cliAuthDeviceCodeTable).where(eq(cliAuthDeviceCodeTable.deviceCode, deviceCode)).limit(1);
98
+ return record ?? null;
99
+ },
100
+ async findDeviceCodeByUserCode(userCode) {
101
+ const [record] = await db.select().from(cliAuthDeviceCodeTable).where(eq(cliAuthDeviceCodeTable.userCode, userCode.toUpperCase())).limit(1);
102
+ return record ?? null;
103
+ },
104
+ async approveDeviceCode(id, userId, rawToken) {
105
+ await db.update(cliAuthDeviceCodeTable).set({
106
+ status: "approved",
107
+ approvedUserId: userId,
108
+ approvedToken: rawToken
109
+ }).where(eq(cliAuthDeviceCodeTable.id, id));
110
+ },
111
+ async clearApprovedToken(id) {
112
+ await db.update(cliAuthDeviceCodeTable).set({ approvedToken: null }).where(eq(cliAuthDeviceCodeTable.id, id));
113
+ }
114
+ });
115
+
116
+ //#endregion
117
+ //#region src/server/use-cases/initiate-device-login.ts
118
+ const makeInitiateDeviceLogin = (deps) => {
119
+ return async (input) => {
120
+ const { cliAuthRepository, logger } = deps;
121
+ const deviceCode = await cliAuthRepository.createDeviceCode(input.scopes);
122
+ logger.info("Device login initiated", { userCode: deviceCode.userCode });
123
+ const appUrl = process.env.APP_URL || "http://localhost:3001";
124
+ return {
125
+ deviceCode: deviceCode.deviceCode,
126
+ userCode: deviceCode.userCode,
127
+ verificationUrl: `${appUrl}/cli/activate`,
128
+ expiresIn: 600,
129
+ intervalSec: deviceCode.intervalSec
130
+ };
131
+ };
132
+ };
133
+
134
+ //#endregion
135
+ //#region src/server/use-cases/poll-device-login.ts
136
+ const makePollDeviceLogin = (deps) => {
137
+ return async (input) => {
138
+ const { cliAuthRepository, userRepository, logger } = deps;
139
+ const deviceCode = await cliAuthRepository.findDeviceCodeByCode(input.deviceCode);
140
+ if (!deviceCode) {
141
+ logger.warn("Poll for unknown device code", { deviceCode: input.deviceCode.slice(0, 8) });
142
+ return { status: "expired" };
143
+ }
144
+ if (deviceCode.status === "pending" && deviceCode.expiresAt < /* @__PURE__ */ new Date()) return { status: "expired" };
145
+ switch (deviceCode.status) {
146
+ case "pending": return { status: "pending" };
147
+ case "approved": {
148
+ if (!deviceCode.approvedToken) return { status: "expired" };
149
+ const user = deviceCode.approvedUserId ? await userRepository.findById(deviceCode.approvedUserId) : null;
150
+ const result = {
151
+ status: "approved",
152
+ token: deviceCode.approvedToken,
153
+ expiresIn: 2160 * 60 * 60,
154
+ scopes: deviceCode.scopes,
155
+ permissions: user?.permissions ? [...user.permissions] : []
156
+ };
157
+ await cliAuthRepository.clearApprovedToken(deviceCode.id);
158
+ logger.info("Device login token retrieved", { deviceCodeId: deviceCode.id });
159
+ return result;
160
+ }
161
+ case "denied": return { status: "denied" };
162
+ case "expired": return { status: "expired" };
163
+ default: return { status: "expired" };
164
+ }
165
+ };
166
+ };
167
+
168
+ //#endregion
169
+ //#region src/server/use-cases/approve-device-login.ts
170
+ const makeApproveDeviceLogin = (deps) => {
171
+ return async (input) => {
172
+ const { cliAuthRepository, logger } = deps;
173
+ const deviceCode = await cliAuthRepository.findDeviceCodeByUserCode(input.userCode);
174
+ if (!deviceCode) return {
175
+ success: false,
176
+ message: "Invalid code"
177
+ };
178
+ if (deviceCode.status !== "pending") return {
179
+ success: false,
180
+ message: "Code already used or expired"
181
+ };
182
+ if (deviceCode.expiresAt < /* @__PURE__ */ new Date()) return {
183
+ success: false,
184
+ message: "Code expired"
185
+ };
186
+ const { token } = await cliAuthRepository.createToken({
187
+ userId: input.userId,
188
+ scopes: deviceCode.scopes,
189
+ name: `CLI Token - ${(/* @__PURE__ */ new Date()).toISOString()}`,
190
+ expiresAt: new Date(Date.now() + 2160 * 60 * 60 * 1e3)
191
+ });
192
+ await cliAuthRepository.approveDeviceCode(deviceCode.id, input.userId, token);
193
+ logger.info("Device login approved", {
194
+ userId: input.userId,
195
+ userCode: input.userCode
196
+ });
197
+ return {
198
+ success: true,
199
+ message: "CLI authorized"
200
+ };
201
+ };
202
+ };
203
+
204
+ //#endregion
205
+ //#region src/server/use-cases/validate-cli-token.ts
206
+ const makeValidateCliToken = (deps) => {
207
+ return async (rawToken) => {
208
+ const { cliAuthRepository } = deps;
209
+ const tokenHash = createHash("sha256").update(rawToken).digest("hex");
210
+ const token = await cliAuthRepository.findTokenByHash(tokenHash);
211
+ if (!token) return null;
212
+ await cliAuthRepository.updateTokenLastUsed(token.id);
213
+ return {
214
+ id: token.id,
215
+ userId: token.userId,
216
+ scopes: token.scopes
217
+ };
218
+ };
219
+ };
220
+
221
+ //#endregion
222
+ //#region src/server/router/cli-device.router.ts
223
+ const initiateInputSchema = z.object({ scopes: z.array(z.string()) });
224
+ const initiateOutputSchema = z.object({
225
+ deviceCode: z.string(),
226
+ userCode: z.string(),
227
+ verificationUrl: z.string(),
228
+ expiresIn: z.number(),
229
+ intervalSec: z.number()
230
+ });
231
+ const pollInputSchema = z.object({ deviceCode: z.string() });
232
+ const pollOutputSchema = z.discriminatedUnion("status", [
233
+ z.object({ status: z.literal("pending") }),
234
+ z.object({
235
+ status: z.literal("approved"),
236
+ token: z.string(),
237
+ expiresIn: z.number(),
238
+ scopes: z.array(z.string()),
239
+ permissions: z.array(z.string())
240
+ }),
241
+ z.object({ status: z.literal("denied") }),
242
+ z.object({ status: z.literal("expired") })
243
+ ]);
244
+ const approveInputSchema = z.object({ userCode: z.string() });
245
+ const approveOutputSchema = z.object({
246
+ success: z.boolean(),
247
+ message: z.string()
248
+ });
249
+ const cliDeviceRouter = {
250
+ initiate: publicProcedure.input(initiateInputSchema).output(initiateOutputSchema).handler(async ({ input, context }) => {
251
+ const { initiateDeviceLogin } = context.di.cradle;
252
+ return initiateDeviceLogin(input);
253
+ }),
254
+ poll: publicProcedure.input(pollInputSchema).output(pollOutputSchema).handler(async ({ input, context }) => {
255
+ const { pollDeviceLogin } = context.di.cradle;
256
+ return pollDeviceLogin(input);
257
+ }),
258
+ approve: protectedProcedure.input(approveInputSchema).output(approveOutputSchema).handler(async ({ input, context }) => {
259
+ const { approveDeviceLogin } = context.di.cradle;
260
+ const userId = context.session.user.id;
261
+ return approveDeviceLogin({
262
+ userCode: input.userCode,
263
+ userId
264
+ });
265
+ })
266
+ };
267
+
268
+ //#endregion
269
+ //#region src/server/middleware/cli-auth.middleware.ts
270
+ const makeCliAuthMiddleware = (deps) => {
271
+ return async (req, res, next) => {
272
+ const authHeader = req.headers.authorization;
273
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
274
+ res.status(401).json({ error: "Missing or invalid authorization header" });
275
+ return;
276
+ }
277
+ const token = authHeader.slice(7);
278
+ const result = await deps.validateCliToken(token);
279
+ if (!result) {
280
+ res.status(401).json({ error: "Invalid or expired token" });
281
+ return;
282
+ }
283
+ req.cliAuth = {
284
+ id: result.id,
285
+ userId: result.userId,
286
+ scopes: result.scopes
287
+ };
288
+ next();
289
+ };
290
+ };
291
+ const requireScope = (scope) => {
292
+ return (req, res, next) => {
293
+ if (!req.cliAuth) {
294
+ res.status(401).json({ error: "Not authenticated" });
295
+ return;
296
+ }
297
+ if (!req.cliAuth.scopes.includes(scope)) {
298
+ res.status(403).json({ error: `Missing required scope: ${scope}` });
299
+ return;
300
+ }
301
+ next();
302
+ };
303
+ };
304
+
305
+ //#endregion
306
+ //#region src/server/module.ts
307
+ const kuckitModule = defineKuckitModule({
308
+ id: "kuckit.cli-auth",
309
+ displayName: "CLI Authentication",
310
+ description: "Device flow authentication for CLI tools",
311
+ version: "0.1.0",
312
+ async register(ctx) {
313
+ const { container } = ctx;
314
+ ctx.registerSchema("cli_auth_token", cliAuthTokenTable);
315
+ ctx.registerSchema("cli_auth_device_code", cliAuthDeviceCodeTable);
316
+ container.register({ cliAuthRepository: asFunction(({ db }) => makeCliAuthRepository(db)).scoped() });
317
+ container.register({
318
+ initiateDeviceLogin: asFunction(({ cliAuthRepository, logger }) => makeInitiateDeviceLogin({
319
+ cliAuthRepository,
320
+ logger
321
+ })).scoped(),
322
+ pollDeviceLogin: asFunction(({ cliAuthRepository, userRepository, logger }) => makePollDeviceLogin({
323
+ cliAuthRepository,
324
+ userRepository,
325
+ logger
326
+ })).scoped(),
327
+ approveDeviceLogin: asFunction(({ cliAuthRepository, logger }) => makeApproveDeviceLogin({
328
+ cliAuthRepository,
329
+ logger
330
+ })).scoped(),
331
+ validateCliToken: asFunction(({ cliAuthRepository }) => makeValidateCliToken({ cliAuthRepository })).scoped()
332
+ });
333
+ },
334
+ registerApi(ctx) {
335
+ ctx.addApiRegistration({
336
+ type: "rpc-router",
337
+ name: "cliDevice",
338
+ router: cliDeviceRouter
339
+ });
340
+ },
341
+ async onBootstrap(ctx) {
342
+ const { container } = ctx;
343
+ container.resolve("logger").info("CLI Auth module initialized");
344
+ },
345
+ async onShutdown(ctx) {
346
+ const { container } = ctx;
347
+ container.resolve("logger").info("CLI Auth module shutting down");
348
+ }
349
+ });
350
+
351
+ //#endregion
352
+ export { kuckitModule, makeCliAuthMiddleware, requireScope };
353
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","names":["result: PollDeviceLoginOutput"],"sources":["../../src/server/schema/cli-auth.ts","../../src/server/repository/helpers.ts","../../src/server/repository/cli-auth.repository.ts","../../src/server/use-cases/initiate-device-login.ts","../../src/server/use-cases/poll-device-login.ts","../../src/server/use-cases/approve-device-login.ts","../../src/server/use-cases/validate-cli-token.ts","../../src/server/router/cli-device.router.ts","../../src/server/middleware/cli-auth.middleware.ts","../../src/server/module.ts"],"sourcesContent":["import { pgTable, pgEnum, text, timestamp, integer, index } from 'drizzle-orm/pg-core'\n\n// Enum for device code status\nexport const deviceCodeStatusEnum = pgEnum('cli_auth_device_code_status', [\n\t'pending',\n\t'approved',\n\t'denied',\n\t'expired',\n])\n\n// CLI authentication tokens table\nexport const cliAuthTokenTable = pgTable(\n\t'cli_auth_token',\n\t{\n\t\tid: text('id').primaryKey(),\n\t\tuserId: text('user_id').notNull(),\n\t\ttokenHash: text('token_hash').notNull().unique(),\n\t\tscopes: text('scopes').array().notNull().default([]),\n\t\tname: text('name'),\n\t\texpiresAt: timestamp('expires_at'),\n\t\tlastUsedAt: timestamp('last_used_at'),\n\t\trevokedAt: timestamp('revoked_at'),\n\t\tcreatedAt: timestamp('created_at').notNull().defaultNow(),\n\t},\n\t(table) => ({\n\t\tuserIdIdx: index('cli_auth_token_user_id_idx').on(table.userId),\n\t})\n)\n\n// Device codes for device flow authentication\nexport const cliAuthDeviceCodeTable = pgTable('cli_auth_device_code', {\n\tid: text('id').primaryKey(),\n\tdeviceCode: text('device_code').notNull().unique(),\n\tuserCode: text('user_code').notNull().unique(),\n\tscopes: text('scopes').array().notNull().default([]),\n\tstatus: deviceCodeStatusEnum('status').notNull().default('pending'),\n\tapprovedUserId: text('approved_user_id'),\n\tapprovedToken: text('approved_token'),\n\tintervalSec: integer('interval_sec').notNull().default(5),\n\texpiresAt: timestamp('expires_at').notNull(),\n\tcreatedAt: timestamp('created_at').notNull().defaultNow(),\n})\n\nexport type CliAuthToken = typeof cliAuthTokenTable.$inferSelect\nexport type NewCliAuthToken = typeof cliAuthTokenTable.$inferInsert\nexport type CliAuthDeviceCode = typeof cliAuthDeviceCodeTable.$inferSelect\nexport type NewCliAuthDeviceCode = typeof cliAuthDeviceCodeTable.$inferInsert\n","import { randomBytes } from 'crypto'\n\n// Alphabet excluding confusing characters: 0, O, 1, I, L\nconst USER_CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'\n\n/**\n * Generate a user-friendly 8-character code in XXXX-XXXX format.\n * Uses crypto.randomBytes for entropy and excludes confusing characters.\n */\nexport function generateUserCode(): string {\n\tconst bytes = randomBytes(8)\n\tlet code = ''\n\tfor (let i = 0; i < 8; i++) {\n\t\tcode += USER_CODE_ALPHABET[bytes[i]! % USER_CODE_ALPHABET.length]\n\t\tif (i === 3) code += '-'\n\t}\n\treturn code\n}\n","import type { NodePgDatabase } from 'drizzle-orm/node-postgres'\nimport { eq, and, isNull, gt, or } from 'drizzle-orm'\nimport { createHash, randomBytes } from 'crypto'\nimport {\n\tcliAuthTokenTable,\n\tcliAuthDeviceCodeTable,\n\ttype CliAuthToken,\n\ttype NewCliAuthToken,\n\ttype CliAuthDeviceCode,\n} from '../schema/cli-auth'\nimport { generateUserCode } from './helpers'\n\nexport interface CliAuthRepository {\n\t// Token operations\n\tcreateToken(\n\t\tdata: Omit<NewCliAuthToken, 'id' | 'tokenHash'>\n\t): Promise<{ token: string; record: CliAuthToken }>\n\tfindTokenByHash(hash: string): Promise<CliAuthToken | null>\n\tupdateTokenLastUsed(id: string): Promise<void>\n\trevokeToken(id: string): Promise<void>\n\n\t// Device code operations\n\tcreateDeviceCode(scopes: string[]): Promise<CliAuthDeviceCode>\n\tfindDeviceCodeByCode(deviceCode: string): Promise<CliAuthDeviceCode | null>\n\tfindDeviceCodeByUserCode(userCode: string): Promise<CliAuthDeviceCode | null>\n\tapproveDeviceCode(id: string, userId: string, rawToken: string): Promise<void>\n\tclearApprovedToken(id: string): Promise<void>\n}\n\nexport const makeCliAuthRepository = (db: NodePgDatabase): CliAuthRepository => ({\n\tasync createToken(data) {\n\t\tconst rawToken = `kuckit_cli_${randomBytes(32).toString('base64url')}`\n\t\tconst tokenHash = createHash('sha256').update(rawToken).digest('hex')\n\t\tconst id = crypto.randomUUID()\n\n\t\tconst [record] = await db\n\t\t\t.insert(cliAuthTokenTable)\n\t\t\t.values({ ...data, id, tokenHash })\n\t\t\t.returning()\n\n\t\treturn { token: rawToken, record: record! }\n\t},\n\n\tasync findTokenByHash(hash) {\n\t\tconst [token] = await db\n\t\t\t.select()\n\t\t\t.from(cliAuthTokenTable)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\teq(cliAuthTokenTable.tokenHash, hash),\n\t\t\t\t\tisNull(cliAuthTokenTable.revokedAt),\n\t\t\t\t\tor(isNull(cliAuthTokenTable.expiresAt), gt(cliAuthTokenTable.expiresAt, new Date()))\n\t\t\t\t)\n\t\t\t)\n\t\t\t.limit(1)\n\t\treturn token ?? null\n\t},\n\n\tasync updateTokenLastUsed(id) {\n\t\tawait db\n\t\t\t.update(cliAuthTokenTable)\n\t\t\t.set({ lastUsedAt: new Date() })\n\t\t\t.where(eq(cliAuthTokenTable.id, id))\n\t},\n\n\tasync revokeToken(id) {\n\t\tawait db\n\t\t\t.update(cliAuthTokenTable)\n\t\t\t.set({ revokedAt: new Date() })\n\t\t\t.where(eq(cliAuthTokenTable.id, id))\n\t},\n\n\tasync createDeviceCode(scopes) {\n\t\tconst id = crypto.randomUUID()\n\t\tconst deviceCode = randomBytes(32).toString('base64url')\n\t\tconst userCode = generateUserCode()\n\n\t\tconst [record] = await db\n\t\t\t.insert(cliAuthDeviceCodeTable)\n\t\t\t.values({\n\t\t\t\tid,\n\t\t\t\tdeviceCode,\n\t\t\t\tuserCode,\n\t\t\t\tscopes,\n\t\t\t\texpiresAt: new Date(Date.now() + 10 * 60 * 1000), // 10 minutes\n\t\t\t})\n\t\t\t.returning()\n\n\t\treturn record!\n\t},\n\n\tasync findDeviceCodeByCode(deviceCode) {\n\t\tconst [record] = await db\n\t\t\t.select()\n\t\t\t.from(cliAuthDeviceCodeTable)\n\t\t\t.where(eq(cliAuthDeviceCodeTable.deviceCode, deviceCode))\n\t\t\t.limit(1)\n\t\treturn record ?? null\n\t},\n\n\tasync findDeviceCodeByUserCode(userCode) {\n\t\tconst [record] = await db\n\t\t\t.select()\n\t\t\t.from(cliAuthDeviceCodeTable)\n\t\t\t.where(eq(cliAuthDeviceCodeTable.userCode, userCode.toUpperCase()))\n\t\t\t.limit(1)\n\t\treturn record ?? null\n\t},\n\n\tasync approveDeviceCode(id, userId, rawToken) {\n\t\tawait db\n\t\t\t.update(cliAuthDeviceCodeTable)\n\t\t\t.set({ status: 'approved', approvedUserId: userId, approvedToken: rawToken })\n\t\t\t.where(eq(cliAuthDeviceCodeTable.id, id))\n\t},\n\n\tasync clearApprovedToken(id) {\n\t\tawait db\n\t\t\t.update(cliAuthDeviceCodeTable)\n\t\t\t.set({ approvedToken: null })\n\t\t\t.where(eq(cliAuthDeviceCodeTable.id, id))\n\t},\n})\n","import type { Logger } from '@kuckit/domain'\nimport type { CliAuthRepository } from '../repository/cli-auth.repository'\n\nexport interface InitiateDeviceLoginDeps {\n\treadonly cliAuthRepository: CliAuthRepository\n\treadonly logger: Logger\n}\n\nexport interface InitiateDeviceLoginInput {\n\treadonly scopes: string[]\n}\n\nexport interface InitiateDeviceLoginOutput {\n\treadonly deviceCode: string\n\treadonly userCode: string\n\treadonly verificationUrl: string\n\treadonly expiresIn: number\n\treadonly intervalSec: number\n}\n\nexport const makeInitiateDeviceLogin = (deps: InitiateDeviceLoginDeps) => {\n\treturn async (input: InitiateDeviceLoginInput): Promise<InitiateDeviceLoginOutput> => {\n\t\tconst { cliAuthRepository, logger } = deps\n\n\t\tconst deviceCode = await cliAuthRepository.createDeviceCode(input.scopes)\n\n\t\tlogger.info('Device login initiated', { userCode: deviceCode.userCode })\n\n\t\tconst appUrl = process.env.APP_URL || 'http://localhost:3001'\n\n\t\treturn {\n\t\t\tdeviceCode: deviceCode.deviceCode,\n\t\t\tuserCode: deviceCode.userCode,\n\t\t\tverificationUrl: `${appUrl}/cli/activate`,\n\t\t\texpiresIn: 600,\n\t\t\tintervalSec: deviceCode.intervalSec,\n\t\t}\n\t}\n}\n","import type { Logger, UserRepository } from '@kuckit/domain'\nimport type { CliAuthRepository } from '../repository/cli-auth.repository'\n\nexport interface PollDeviceLoginDeps {\n\treadonly cliAuthRepository: CliAuthRepository\n\treadonly userRepository: UserRepository\n\treadonly logger: Logger\n}\n\nexport interface PollDeviceLoginInput {\n\treadonly deviceCode: string\n}\n\nexport type PollDeviceLoginOutput =\n\t| { status: 'pending' }\n\t| {\n\t\t\tstatus: 'approved'\n\t\t\ttoken: string\n\t\t\texpiresIn: number\n\t\t\tscopes: string[]\n\t\t\tpermissions: string[]\n\t }\n\t| { status: 'denied' }\n\t| { status: 'expired' }\n\nexport const makePollDeviceLogin = (deps: PollDeviceLoginDeps) => {\n\treturn async (input: PollDeviceLoginInput): Promise<PollDeviceLoginOutput> => {\n\t\tconst { cliAuthRepository, userRepository, logger } = deps\n\n\t\tconst deviceCode = await cliAuthRepository.findDeviceCodeByCode(input.deviceCode)\n\n\t\tif (!deviceCode) {\n\t\t\tlogger.warn('Poll for unknown device code', { deviceCode: input.deviceCode.slice(0, 8) })\n\t\t\treturn { status: 'expired' }\n\t\t}\n\n\t\t// Check if expired (only for pending status)\n\t\tif (deviceCode.status === 'pending' && deviceCode.expiresAt < new Date()) {\n\t\t\treturn { status: 'expired' }\n\t\t}\n\n\t\tswitch (deviceCode.status) {\n\t\t\tcase 'pending':\n\t\t\t\treturn { status: 'pending' }\n\n\t\t\tcase 'approved': {\n\t\t\t\tif (!deviceCode.approvedToken) {\n\t\t\t\t\t// Token already retrieved\n\t\t\t\t\treturn { status: 'expired' }\n\t\t\t\t}\n\n\t\t\t\t// Fetch user to get permissions\n\t\t\t\tconst user = deviceCode.approvedUserId\n\t\t\t\t\t? await userRepository.findById(deviceCode.approvedUserId)\n\t\t\t\t\t: null\n\n\t\t\t\t// Calculate remaining time for token expiry (90 days from approval)\n\t\t\t\tconst expiresIn = 90 * 24 * 60 * 60\n\n\t\t\t\tconst result: PollDeviceLoginOutput = {\n\t\t\t\t\tstatus: 'approved',\n\t\t\t\t\ttoken: deviceCode.approvedToken,\n\t\t\t\t\texpiresIn,\n\t\t\t\t\tscopes: deviceCode.scopes,\n\t\t\t\t\tpermissions: user?.permissions ? [...user.permissions] : [],\n\t\t\t\t}\n\n\t\t\t\t// Clear the token after retrieval (one-time read)\n\t\t\t\tawait cliAuthRepository.clearApprovedToken(deviceCode.id)\n\n\t\t\t\tlogger.info('Device login token retrieved', { deviceCodeId: deviceCode.id })\n\n\t\t\t\treturn result\n\t\t\t}\n\n\t\t\tcase 'denied':\n\t\t\t\treturn { status: 'denied' }\n\n\t\t\tcase 'expired':\n\t\t\t\treturn { status: 'expired' }\n\n\t\t\tdefault:\n\t\t\t\treturn { status: 'expired' }\n\t\t}\n\t}\n}\n","import type { Logger } from '@kuckit/domain'\nimport type { CliAuthRepository } from '../repository/cli-auth.repository'\n\nexport interface ApproveDeviceLoginDeps {\n\treadonly cliAuthRepository: CliAuthRepository\n\treadonly logger: Logger\n}\n\nexport interface ApproveDeviceLoginInput {\n\treadonly userCode: string\n\treadonly userId: string\n}\n\nexport interface ApproveDeviceLoginOutput {\n\treadonly success: boolean\n\treadonly message: string\n}\n\nexport const makeApproveDeviceLogin = (deps: ApproveDeviceLoginDeps) => {\n\treturn async (input: ApproveDeviceLoginInput): Promise<ApproveDeviceLoginOutput> => {\n\t\tconst { cliAuthRepository, logger } = deps\n\n\t\tconst deviceCode = await cliAuthRepository.findDeviceCodeByUserCode(input.userCode)\n\n\t\tif (!deviceCode) {\n\t\t\treturn { success: false, message: 'Invalid code' }\n\t\t}\n\n\t\tif (deviceCode.status !== 'pending') {\n\t\t\treturn { success: false, message: 'Code already used or expired' }\n\t\t}\n\n\t\tif (deviceCode.expiresAt < new Date()) {\n\t\t\treturn { success: false, message: 'Code expired' }\n\t\t}\n\n\t\t// Create CLI token with 90-day expiry\n\t\tconst { token } = await cliAuthRepository.createToken({\n\t\t\tuserId: input.userId,\n\t\t\tscopes: deviceCode.scopes,\n\t\t\tname: `CLI Token - ${new Date().toISOString()}`,\n\t\t\texpiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),\n\t\t})\n\n\t\t// Mark device code as approved (store raw token for polling)\n\t\tawait cliAuthRepository.approveDeviceCode(deviceCode.id, input.userId, token)\n\n\t\tlogger.info('Device login approved', { userId: input.userId, userCode: input.userCode })\n\n\t\treturn { success: true, message: 'CLI authorized' }\n\t}\n}\n","import { createHash } from 'crypto'\nimport type { CliAuthRepository } from '../repository/cli-auth.repository'\n\nexport interface ValidateCliTokenDeps {\n\treadonly cliAuthRepository: CliAuthRepository\n}\n\nexport interface ValidateCliTokenOutput {\n\treadonly id: string\n\treadonly userId: string\n\treadonly scopes: string[]\n}\n\nexport const makeValidateCliToken = (deps: ValidateCliTokenDeps) => {\n\treturn async (rawToken: string): Promise<ValidateCliTokenOutput | null> => {\n\t\tconst { cliAuthRepository } = deps\n\n\t\t// Hash the token\n\t\tconst tokenHash = createHash('sha256').update(rawToken).digest('hex')\n\n\t\t// Look up token by hash\n\t\tconst token = await cliAuthRepository.findTokenByHash(tokenHash)\n\n\t\tif (!token) {\n\t\t\treturn null\n\t\t}\n\n\t\t// Update last used timestamp\n\t\tawait cliAuthRepository.updateTokenLastUsed(token.id)\n\n\t\treturn {\n\t\t\tid: token.id,\n\t\t\tuserId: token.userId,\n\t\t\tscopes: token.scopes,\n\t\t}\n\t}\n}\n","import { publicProcedure, protectedProcedure } from '@kuckit/api'\nimport { z } from 'zod'\nimport type {\n\tInitiateDeviceLoginInput,\n\tInitiateDeviceLoginOutput,\n} from '../use-cases/initiate-device-login'\nimport type { PollDeviceLoginInput, PollDeviceLoginOutput } from '../use-cases/poll-device-login'\nimport type {\n\tApproveDeviceLoginInput,\n\tApproveDeviceLoginOutput,\n} from '../use-cases/approve-device-login'\n\nconst initiateInputSchema = z.object({\n\tscopes: z.array(z.string()),\n})\n\nconst initiateOutputSchema = z.object({\n\tdeviceCode: z.string(),\n\tuserCode: z.string(),\n\tverificationUrl: z.string(),\n\texpiresIn: z.number(),\n\tintervalSec: z.number(),\n})\n\nconst pollInputSchema = z.object({\n\tdeviceCode: z.string(),\n})\n\nconst pollOutputSchema = z.discriminatedUnion('status', [\n\tz.object({ status: z.literal('pending') }),\n\tz.object({\n\t\tstatus: z.literal('approved'),\n\t\ttoken: z.string(),\n\t\texpiresIn: z.number(),\n\t\tscopes: z.array(z.string()),\n\t\tpermissions: z.array(z.string()),\n\t}),\n\tz.object({ status: z.literal('denied') }),\n\tz.object({ status: z.literal('expired') }),\n])\n\nconst approveInputSchema = z.object({\n\tuserCode: z.string(),\n})\n\nconst approveOutputSchema = z.object({\n\tsuccess: z.boolean(),\n\tmessage: z.string(),\n})\n\ninterface CliAuthCradle {\n\tinitiateDeviceLogin: (input: InitiateDeviceLoginInput) => Promise<InitiateDeviceLoginOutput>\n\tpollDeviceLogin: (input: PollDeviceLoginInput) => Promise<PollDeviceLoginOutput>\n\tapproveDeviceLogin: (input: ApproveDeviceLoginInput) => Promise<ApproveDeviceLoginOutput>\n}\n\nexport const cliDeviceRouter = {\n\tinitiate: publicProcedure\n\t\t.input(initiateInputSchema)\n\t\t.output(initiateOutputSchema)\n\t\t.handler(async ({ input, context }): Promise<InitiateDeviceLoginOutput> => {\n\t\t\tconst { initiateDeviceLogin } = context.di.cradle as CliAuthCradle\n\t\t\treturn initiateDeviceLogin(input)\n\t\t}),\n\n\tpoll: publicProcedure\n\t\t.input(pollInputSchema)\n\t\t.output(pollOutputSchema)\n\t\t.handler(async ({ input, context }): Promise<PollDeviceLoginOutput> => {\n\t\t\tconst { pollDeviceLogin } = context.di.cradle as CliAuthCradle\n\t\t\treturn pollDeviceLogin(input)\n\t\t}),\n\n\tapprove: protectedProcedure\n\t\t.input(approveInputSchema)\n\t\t.output(approveOutputSchema)\n\t\t.handler(async ({ input, context }): Promise<ApproveDeviceLoginOutput> => {\n\t\t\tconst { approveDeviceLogin } = context.di.cradle as CliAuthCradle\n\t\t\tconst userId = context.session.user.id\n\t\t\treturn approveDeviceLogin({ userCode: input.userCode, userId })\n\t\t}),\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport type { ValidateCliTokenOutput } from '../use-cases/validate-cli-token'\n\nexport interface CliAuthContext {\n\tid: string\n\tuserId: string\n\tscopes: string[]\n}\n\ndeclare global {\n\t// eslint-disable-next-line @typescript-eslint/no-namespace\n\tnamespace Express {\n\t\tinterface Request {\n\t\t\tcliAuth?: CliAuthContext\n\t\t}\n\t}\n}\n\ntype ValidateCliToken = (rawToken: string) => Promise<ValidateCliTokenOutput | null>\n\nexport interface CliAuthMiddlewareDeps {\n\tvalidateCliToken: ValidateCliToken\n}\n\nexport const makeCliAuthMiddleware = (deps: CliAuthMiddlewareDeps) => {\n\treturn async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n\t\tconst authHeader = req.headers.authorization\n\n\t\tif (!authHeader || !authHeader.startsWith('Bearer ')) {\n\t\t\tres.status(401).json({ error: 'Missing or invalid authorization header' })\n\t\t\treturn\n\t\t}\n\n\t\tconst token = authHeader.slice(7)\n\n\t\tconst result = await deps.validateCliToken(token)\n\n\t\tif (!result) {\n\t\t\tres.status(401).json({ error: 'Invalid or expired token' })\n\t\t\treturn\n\t\t}\n\n\t\treq.cliAuth = {\n\t\t\tid: result.id,\n\t\t\tuserId: result.userId,\n\t\t\tscopes: result.scopes,\n\t\t}\n\n\t\tnext()\n\t}\n}\n\nexport const requireScope = (scope: string) => {\n\treturn (req: Request, res: Response, next: NextFunction): void => {\n\t\tif (!req.cliAuth) {\n\t\t\tres.status(401).json({ error: 'Not authenticated' })\n\t\t\treturn\n\t\t}\n\n\t\tif (!req.cliAuth.scopes.includes(scope)) {\n\t\t\tres.status(403).json({ error: `Missing required scope: ${scope}` })\n\t\t\treturn\n\t\t}\n\n\t\tnext()\n\t}\n}\n","import { defineKuckitModule, asFunction, type KuckitModuleContext } from '@kuckit/sdk'\nimport { cliAuthTokenTable, cliAuthDeviceCodeTable } from './schema'\nimport { makeCliAuthRepository } from './repository'\nimport {\n\tmakeInitiateDeviceLogin,\n\tmakePollDeviceLogin,\n\tmakeApproveDeviceLogin,\n\tmakeValidateCliToken,\n} from './use-cases'\nimport { cliDeviceRouter } from './router'\n\nexport { makeCliAuthMiddleware, requireScope, type CliAuthContext } from './middleware'\n\nexport type CliAuthModuleConfig = Record<string, never>\n\nexport const kuckitModule = defineKuckitModule<CliAuthModuleConfig>({\n\tid: 'kuckit.cli-auth',\n\tdisplayName: 'CLI Authentication',\n\tdescription: 'Device flow authentication for CLI tools',\n\tversion: '0.1.0',\n\n\tasync register(ctx: KuckitModuleContext<CliAuthModuleConfig>) {\n\t\tconst { container } = ctx\n\n\t\t// Register database schemas\n\t\tctx.registerSchema('cli_auth_token', cliAuthTokenTable)\n\t\tctx.registerSchema('cli_auth_device_code', cliAuthDeviceCodeTable)\n\n\t\t// Register repository\n\t\tcontainer.register({\n\t\t\tcliAuthRepository: asFunction(({ db }) => makeCliAuthRepository(db)).scoped(),\n\t\t})\n\n\t\t// Register use cases\n\t\tcontainer.register({\n\t\t\tinitiateDeviceLogin: asFunction(({ cliAuthRepository, logger }) =>\n\t\t\t\tmakeInitiateDeviceLogin({ cliAuthRepository, logger })\n\t\t\t).scoped(),\n\t\t\tpollDeviceLogin: asFunction(({ cliAuthRepository, userRepository, logger }) =>\n\t\t\t\tmakePollDeviceLogin({ cliAuthRepository, userRepository, logger })\n\t\t\t).scoped(),\n\t\t\tapproveDeviceLogin: asFunction(({ cliAuthRepository, logger }) =>\n\t\t\t\tmakeApproveDeviceLogin({ cliAuthRepository, logger })\n\t\t\t).scoped(),\n\t\t\tvalidateCliToken: asFunction(({ cliAuthRepository }) =>\n\t\t\t\tmakeValidateCliToken({ cliAuthRepository })\n\t\t\t).scoped(),\n\t\t})\n\t},\n\n\tregisterApi(ctx) {\n\t\tctx.addApiRegistration({\n\t\t\ttype: 'rpc-router',\n\t\t\tname: 'cliDevice',\n\t\t\trouter: cliDeviceRouter,\n\t\t})\n\t},\n\n\tasync onBootstrap(ctx: KuckitModuleContext<CliAuthModuleConfig>) {\n\t\tconst { container } = ctx\n\t\tconst logger = container.resolve('logger')\n\t\tlogger.info('CLI Auth module initialized')\n\t},\n\n\tasync onShutdown(ctx: KuckitModuleContext<CliAuthModuleConfig>) {\n\t\tconst { container } = ctx\n\t\tconst logger = container.resolve('logger')\n\t\tlogger.info('CLI Auth module shutting down')\n\t},\n})\n"],"mappings":";;;;;;;;AAGA,MAAa,uBAAuB,OAAO,+BAA+B;CACzE;CACA;CACA;CACA;CACA,CAAC;AAGF,MAAa,oBAAoB,QAChC,kBACA;CACC,IAAI,KAAK,KAAK,CAAC,YAAY;CAC3B,QAAQ,KAAK,UAAU,CAAC,SAAS;CACjC,WAAW,KAAK,aAAa,CAAC,SAAS,CAAC,QAAQ;CAChD,QAAQ,KAAK,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;CACpD,MAAM,KAAK,OAAO;CAClB,WAAW,UAAU,aAAa;CAClC,YAAY,UAAU,eAAe;CACrC,WAAW,UAAU,aAAa;CAClC,WAAW,UAAU,aAAa,CAAC,SAAS,CAAC,YAAY;CACzD,GACA,WAAW,EACX,WAAW,MAAM,6BAA6B,CAAC,GAAG,MAAM,OAAO,EAC/D,EACD;AAGD,MAAa,yBAAyB,QAAQ,wBAAwB;CACrE,IAAI,KAAK,KAAK,CAAC,YAAY;CAC3B,YAAY,KAAK,cAAc,CAAC,SAAS,CAAC,QAAQ;CAClD,UAAU,KAAK,YAAY,CAAC,SAAS,CAAC,QAAQ;CAC9C,QAAQ,KAAK,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;CACpD,QAAQ,qBAAqB,SAAS,CAAC,SAAS,CAAC,QAAQ,UAAU;CACnE,gBAAgB,KAAK,mBAAmB;CACxC,eAAe,KAAK,iBAAiB;CACrC,aAAa,QAAQ,eAAe,CAAC,SAAS,CAAC,QAAQ,EAAE;CACzD,WAAW,UAAU,aAAa,CAAC,SAAS;CAC5C,WAAW,UAAU,aAAa,CAAC,SAAS,CAAC,YAAY;CACzD,CAAC;;;;ACtCF,MAAM,qBAAqB;;;;;AAM3B,SAAgB,mBAA2B;CAC1C,MAAM,QAAQ,YAAY,EAAE;CAC5B,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;AAC3B,UAAQ,mBAAmB,MAAM,KAAM;AACvC,MAAI,MAAM,EAAG,SAAQ;;AAEtB,QAAO;;;;;ACaR,MAAa,yBAAyB,QAA2C;CAChF,MAAM,YAAY,MAAM;EACvB,MAAM,WAAW,cAAc,YAAY,GAAG,CAAC,SAAS,YAAY;EACpE,MAAM,YAAY,WAAW,SAAS,CAAC,OAAO,SAAS,CAAC,OAAO,MAAM;EACrE,MAAM,KAAK,OAAO,YAAY;EAE9B,MAAM,CAAC,UAAU,MAAM,GACrB,OAAO,kBAAkB,CACzB,OAAO;GAAE,GAAG;GAAM;GAAI;GAAW,CAAC,CAClC,WAAW;AAEb,SAAO;GAAE,OAAO;GAAkB;GAAS;;CAG5C,MAAM,gBAAgB,MAAM;EAC3B,MAAM,CAAC,SAAS,MAAM,GACpB,QAAQ,CACR,KAAK,kBAAkB,CACvB,MACA,IACC,GAAG,kBAAkB,WAAW,KAAK,EACrC,OAAO,kBAAkB,UAAU,EACnC,GAAG,OAAO,kBAAkB,UAAU,EAAE,GAAG,kBAAkB,2BAAW,IAAI,MAAM,CAAC,CAAC,CACpF,CACD,CACA,MAAM,EAAE;AACV,SAAO,SAAS;;CAGjB,MAAM,oBAAoB,IAAI;AAC7B,QAAM,GACJ,OAAO,kBAAkB,CACzB,IAAI,EAAE,4BAAY,IAAI,MAAM,EAAE,CAAC,CAC/B,MAAM,GAAG,kBAAkB,IAAI,GAAG,CAAC;;CAGtC,MAAM,YAAY,IAAI;AACrB,QAAM,GACJ,OAAO,kBAAkB,CACzB,IAAI,EAAE,2BAAW,IAAI,MAAM,EAAE,CAAC,CAC9B,MAAM,GAAG,kBAAkB,IAAI,GAAG,CAAC;;CAGtC,MAAM,iBAAiB,QAAQ;EAC9B,MAAM,KAAK,OAAO,YAAY;EAC9B,MAAM,aAAa,YAAY,GAAG,CAAC,SAAS,YAAY;EACxD,MAAM,WAAW,kBAAkB;EAEnC,MAAM,CAAC,UAAU,MAAM,GACrB,OAAO,uBAAuB,CAC9B,OAAO;GACP;GACA;GACA;GACA;GACA,WAAW,IAAI,KAAK,KAAK,KAAK,GAAG,MAAU,IAAK;GAChD,CAAC,CACD,WAAW;AAEb,SAAO;;CAGR,MAAM,qBAAqB,YAAY;EACtC,MAAM,CAAC,UAAU,MAAM,GACrB,QAAQ,CACR,KAAK,uBAAuB,CAC5B,MAAM,GAAG,uBAAuB,YAAY,WAAW,CAAC,CACxD,MAAM,EAAE;AACV,SAAO,UAAU;;CAGlB,MAAM,yBAAyB,UAAU;EACxC,MAAM,CAAC,UAAU,MAAM,GACrB,QAAQ,CACR,KAAK,uBAAuB,CAC5B,MAAM,GAAG,uBAAuB,UAAU,SAAS,aAAa,CAAC,CAAC,CAClE,MAAM,EAAE;AACV,SAAO,UAAU;;CAGlB,MAAM,kBAAkB,IAAI,QAAQ,UAAU;AAC7C,QAAM,GACJ,OAAO,uBAAuB,CAC9B,IAAI;GAAE,QAAQ;GAAY,gBAAgB;GAAQ,eAAe;GAAU,CAAC,CAC5E,MAAM,GAAG,uBAAuB,IAAI,GAAG,CAAC;;CAG3C,MAAM,mBAAmB,IAAI;AAC5B,QAAM,GACJ,OAAO,uBAAuB,CAC9B,IAAI,EAAE,eAAe,MAAM,CAAC,CAC5B,MAAM,GAAG,uBAAuB,IAAI,GAAG,CAAC;;CAE3C;;;;ACtGD,MAAa,2BAA2B,SAAkC;AACzE,QAAO,OAAO,UAAwE;EACrF,MAAM,EAAE,mBAAmB,WAAW;EAEtC,MAAM,aAAa,MAAM,kBAAkB,iBAAiB,MAAM,OAAO;AAEzE,SAAO,KAAK,0BAA0B,EAAE,UAAU,WAAW,UAAU,CAAC;EAExE,MAAM,SAAS,QAAQ,IAAI,WAAW;AAEtC,SAAO;GACN,YAAY,WAAW;GACvB,UAAU,WAAW;GACrB,iBAAiB,GAAG,OAAO;GAC3B,WAAW;GACX,aAAa,WAAW;GACxB;;;;;;ACXH,MAAa,uBAAuB,SAA8B;AACjE,QAAO,OAAO,UAAgE;EAC7E,MAAM,EAAE,mBAAmB,gBAAgB,WAAW;EAEtD,MAAM,aAAa,MAAM,kBAAkB,qBAAqB,MAAM,WAAW;AAEjF,MAAI,CAAC,YAAY;AAChB,UAAO,KAAK,gCAAgC,EAAE,YAAY,MAAM,WAAW,MAAM,GAAG,EAAE,EAAE,CAAC;AACzF,UAAO,EAAE,QAAQ,WAAW;;AAI7B,MAAI,WAAW,WAAW,aAAa,WAAW,4BAAY,IAAI,MAAM,CACvE,QAAO,EAAE,QAAQ,WAAW;AAG7B,UAAQ,WAAW,QAAnB;GACC,KAAK,UACJ,QAAO,EAAE,QAAQ,WAAW;GAE7B,KAAK,YAAY;AAChB,QAAI,CAAC,WAAW,cAEf,QAAO,EAAE,QAAQ,WAAW;IAI7B,MAAM,OAAO,WAAW,iBACrB,MAAM,eAAe,SAAS,WAAW,eAAe,GACxD;IAKH,MAAMA,SAAgC;KACrC,QAAQ;KACR,OAAO,WAAW;KAClB,WALiB,OAAU,KAAK;KAMhC,QAAQ,WAAW;KACnB,aAAa,MAAM,cAAc,CAAC,GAAG,KAAK,YAAY,GAAG,EAAE;KAC3D;AAGD,UAAM,kBAAkB,mBAAmB,WAAW,GAAG;AAEzD,WAAO,KAAK,gCAAgC,EAAE,cAAc,WAAW,IAAI,CAAC;AAE5E,WAAO;;GAGR,KAAK,SACJ,QAAO,EAAE,QAAQ,UAAU;GAE5B,KAAK,UACJ,QAAO,EAAE,QAAQ,WAAW;GAE7B,QACC,QAAO,EAAE,QAAQ,WAAW;;;;;;;AChEhC,MAAa,0BAA0B,SAAiC;AACvE,QAAO,OAAO,UAAsE;EACnF,MAAM,EAAE,mBAAmB,WAAW;EAEtC,MAAM,aAAa,MAAM,kBAAkB,yBAAyB,MAAM,SAAS;AAEnF,MAAI,CAAC,WACJ,QAAO;GAAE,SAAS;GAAO,SAAS;GAAgB;AAGnD,MAAI,WAAW,WAAW,UACzB,QAAO;GAAE,SAAS;GAAO,SAAS;GAAgC;AAGnE,MAAI,WAAW,4BAAY,IAAI,MAAM,CACpC,QAAO;GAAE,SAAS;GAAO,SAAS;GAAgB;EAInD,MAAM,EAAE,UAAU,MAAM,kBAAkB,YAAY;GACrD,QAAQ,MAAM;GACd,QAAQ,WAAW;GACnB,MAAM,gCAAe,IAAI,MAAM,EAAC,aAAa;GAC7C,WAAW,IAAI,KAAK,KAAK,KAAK,GAAG,OAAU,KAAK,KAAK,IAAK;GAC1D,CAAC;AAGF,QAAM,kBAAkB,kBAAkB,WAAW,IAAI,MAAM,QAAQ,MAAM;AAE7E,SAAO,KAAK,yBAAyB;GAAE,QAAQ,MAAM;GAAQ,UAAU,MAAM;GAAU,CAAC;AAExF,SAAO;GAAE,SAAS;GAAM,SAAS;GAAkB;;;;;;ACpCrD,MAAa,wBAAwB,SAA+B;AACnE,QAAO,OAAO,aAA6D;EAC1E,MAAM,EAAE,sBAAsB;EAG9B,MAAM,YAAY,WAAW,SAAS,CAAC,OAAO,SAAS,CAAC,OAAO,MAAM;EAGrE,MAAM,QAAQ,MAAM,kBAAkB,gBAAgB,UAAU;AAEhE,MAAI,CAAC,MACJ,QAAO;AAIR,QAAM,kBAAkB,oBAAoB,MAAM,GAAG;AAErD,SAAO;GACN,IAAI,MAAM;GACV,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd;;;;;;ACtBH,MAAM,sBAAsB,EAAE,OAAO,EACpC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAC3B,CAAC;AAEF,MAAM,uBAAuB,EAAE,OAAO;CACrC,YAAY,EAAE,QAAQ;CACtB,UAAU,EAAE,QAAQ;CACpB,iBAAiB,EAAE,QAAQ;CAC3B,WAAW,EAAE,QAAQ;CACrB,aAAa,EAAE,QAAQ;CACvB,CAAC;AAEF,MAAM,kBAAkB,EAAE,OAAO,EAChC,YAAY,EAAE,QAAQ,EACtB,CAAC;AAEF,MAAM,mBAAmB,EAAE,mBAAmB,UAAU;CACvD,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,UAAU,EAAE,CAAC;CAC1C,EAAE,OAAO;EACR,QAAQ,EAAE,QAAQ,WAAW;EAC7B,OAAO,EAAE,QAAQ;EACjB,WAAW,EAAE,QAAQ;EACrB,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;EAC3B,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC;EAChC,CAAC;CACF,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,SAAS,EAAE,CAAC;CACzC,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,UAAU,EAAE,CAAC;CAC1C,CAAC;AAEF,MAAM,qBAAqB,EAAE,OAAO,EACnC,UAAU,EAAE,QAAQ,EACpB,CAAC;AAEF,MAAM,sBAAsB,EAAE,OAAO;CACpC,SAAS,EAAE,SAAS;CACpB,SAAS,EAAE,QAAQ;CACnB,CAAC;AAQF,MAAa,kBAAkB;CAC9B,UAAU,gBACR,MAAM,oBAAoB,CAC1B,OAAO,qBAAqB,CAC5B,QAAQ,OAAO,EAAE,OAAO,cAAkD;EAC1E,MAAM,EAAE,wBAAwB,QAAQ,GAAG;AAC3C,SAAO,oBAAoB,MAAM;GAChC;CAEH,MAAM,gBACJ,MAAM,gBAAgB,CACtB,OAAO,iBAAiB,CACxB,QAAQ,OAAO,EAAE,OAAO,cAA8C;EACtE,MAAM,EAAE,oBAAoB,QAAQ,GAAG;AACvC,SAAO,gBAAgB,MAAM;GAC5B;CAEH,SAAS,mBACP,MAAM,mBAAmB,CACzB,OAAO,oBAAoB,CAC3B,QAAQ,OAAO,EAAE,OAAO,cAAiD;EACzE,MAAM,EAAE,uBAAuB,QAAQ,GAAG;EAC1C,MAAM,SAAS,QAAQ,QAAQ,KAAK;AACpC,SAAO,mBAAmB;GAAE,UAAU,MAAM;GAAU;GAAQ,CAAC;GAC9D;CACH;;;;ACzDD,MAAa,yBAAyB,SAAgC;AACrE,QAAO,OAAO,KAAc,KAAe,SAAsC;EAChF,MAAM,aAAa,IAAI,QAAQ;AAE/B,MAAI,CAAC,cAAc,CAAC,WAAW,WAAW,UAAU,EAAE;AACrD,OAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,2CAA2C,CAAC;AAC1E;;EAGD,MAAM,QAAQ,WAAW,MAAM,EAAE;EAEjC,MAAM,SAAS,MAAM,KAAK,iBAAiB,MAAM;AAEjD,MAAI,CAAC,QAAQ;AACZ,OAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,4BAA4B,CAAC;AAC3D;;AAGD,MAAI,UAAU;GACb,IAAI,OAAO;GACX,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf;AAED,QAAM;;;AAIR,MAAa,gBAAgB,UAAkB;AAC9C,SAAQ,KAAc,KAAe,SAA6B;AACjE,MAAI,CAAC,IAAI,SAAS;AACjB,OAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,qBAAqB,CAAC;AACpD;;AAGD,MAAI,CAAC,IAAI,QAAQ,OAAO,SAAS,MAAM,EAAE;AACxC,OAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,2BAA2B,SAAS,CAAC;AACnE;;AAGD,QAAM;;;;;;ACjDR,MAAa,eAAe,mBAAwC;CACnE,IAAI;CACJ,aAAa;CACb,aAAa;CACb,SAAS;CAET,MAAM,SAAS,KAA+C;EAC7D,MAAM,EAAE,cAAc;AAGtB,MAAI,eAAe,kBAAkB,kBAAkB;AACvD,MAAI,eAAe,wBAAwB,uBAAuB;AAGlE,YAAU,SAAS,EAClB,mBAAmB,YAAY,EAAE,SAAS,sBAAsB,GAAG,CAAC,CAAC,QAAQ,EAC7E,CAAC;AAGF,YAAU,SAAS;GAClB,qBAAqB,YAAY,EAAE,mBAAmB,aACrD,wBAAwB;IAAE;IAAmB;IAAQ,CAAC,CACtD,CAAC,QAAQ;GACV,iBAAiB,YAAY,EAAE,mBAAmB,gBAAgB,aACjE,oBAAoB;IAAE;IAAmB;IAAgB;IAAQ,CAAC,CAClE,CAAC,QAAQ;GACV,oBAAoB,YAAY,EAAE,mBAAmB,aACpD,uBAAuB;IAAE;IAAmB;IAAQ,CAAC,CACrD,CAAC,QAAQ;GACV,kBAAkB,YAAY,EAAE,wBAC/B,qBAAqB,EAAE,mBAAmB,CAAC,CAC3C,CAAC,QAAQ;GACV,CAAC;;CAGH,YAAY,KAAK;AAChB,MAAI,mBAAmB;GACtB,MAAM;GACN,MAAM;GACN,QAAQ;GACR,CAAC;;CAGH,MAAM,YAAY,KAA+C;EAChE,MAAM,EAAE,cAAc;AAEtB,EADe,UAAU,QAAQ,SAAS,CACnC,KAAK,8BAA8B;;CAG3C,MAAM,WAAW,KAA+C;EAC/D,MAAM,EAAE,cAAc;AAEtB,EADe,UAAU,QAAQ,SAAS,CACnC,KAAK,gCAAgC;;CAE7C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@kuckit/cli-auth-module",
3
+ "version": "1.0.0",
4
+ "description": "Device flow authentication module for Kuckit CLI tools",
5
+ "type": "module",
6
+ "main": "src/server/module.ts",
7
+ "types": "src/server/module.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./src/server/module.ts",
14
+ "default": "./src/server/module.ts"
15
+ },
16
+ "./client": {
17
+ "types": "./src/client/index.ts",
18
+ "default": "./src/client/index.ts"
19
+ }
20
+ },
21
+ "publishConfig": {
22
+ "main": "dist/server/module.js",
23
+ "types": "dist/server/module.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/server/module.d.ts",
27
+ "default": "./dist/server/module.js"
28
+ },
29
+ "./client": {
30
+ "types": "./dist/client/index.d.ts",
31
+ "default": "./dist/client/index.js"
32
+ }
33
+ }
34
+ },
35
+ "kuckit": {
36
+ "id": "kuckit.cli-auth",
37
+ "server": ".",
38
+ "client": "./client"
39
+ },
40
+ "scripts": {
41
+ "build": "tsdown"
42
+ },
43
+ "peerDependencies": {
44
+ "@kuckit/sdk": "workspace:*",
45
+ "@kuckit/sdk-react": "workspace:*",
46
+ "react": "^18 || ^19",
47
+ "typescript": "^5",
48
+ "express": "^4 || ^5"
49
+ },
50
+ "dependencies": {
51
+ "@kuckit/api": "workspace:*",
52
+ "@kuckit/domain": "workspace:*",
53
+ "drizzle-orm": "^0.44.2",
54
+ "zod": "catalog:"
55
+ },
56
+ "devDependencies": {
57
+ "@types/react": "^19.0.0",
58
+ "@types/express": "catalog:",
59
+ "tsdown": "catalog:"
60
+ }
61
+ }
@@ -0,0 +1,70 @@
1
+ import { defineKuckitModule, asFunction, type KuckitModuleContext } from '@kuckit/sdk'
2
+ import { cliAuthTokenTable, cliAuthDeviceCodeTable } from './schema'
3
+ import { makeCliAuthRepository } from './repository'
4
+ import {
5
+ makeInitiateDeviceLogin,
6
+ makePollDeviceLogin,
7
+ makeApproveDeviceLogin,
8
+ makeValidateCliToken,
9
+ } from './use-cases'
10
+ import { cliDeviceRouter } from './router'
11
+
12
+ export { makeCliAuthMiddleware, requireScope, type CliAuthContext } from './middleware'
13
+
14
+ export type CliAuthModuleConfig = Record<string, never>
15
+
16
+ export const kuckitModule = defineKuckitModule<CliAuthModuleConfig>({
17
+ id: 'kuckit.cli-auth',
18
+ displayName: 'CLI Authentication',
19
+ description: 'Device flow authentication for CLI tools',
20
+ version: '0.1.0',
21
+
22
+ async register(ctx: KuckitModuleContext<CliAuthModuleConfig>) {
23
+ const { container } = ctx
24
+
25
+ // Register database schemas
26
+ ctx.registerSchema('cli_auth_token', cliAuthTokenTable)
27
+ ctx.registerSchema('cli_auth_device_code', cliAuthDeviceCodeTable)
28
+
29
+ // Register repository
30
+ container.register({
31
+ cliAuthRepository: asFunction(({ db }) => makeCliAuthRepository(db)).scoped(),
32
+ })
33
+
34
+ // Register use cases
35
+ container.register({
36
+ initiateDeviceLogin: asFunction(({ cliAuthRepository, logger }) =>
37
+ makeInitiateDeviceLogin({ cliAuthRepository, logger })
38
+ ).scoped(),
39
+ pollDeviceLogin: asFunction(({ cliAuthRepository, userRepository, logger }) =>
40
+ makePollDeviceLogin({ cliAuthRepository, userRepository, logger })
41
+ ).scoped(),
42
+ approveDeviceLogin: asFunction(({ cliAuthRepository, logger }) =>
43
+ makeApproveDeviceLogin({ cliAuthRepository, logger })
44
+ ).scoped(),
45
+ validateCliToken: asFunction(({ cliAuthRepository }) =>
46
+ makeValidateCliToken({ cliAuthRepository })
47
+ ).scoped(),
48
+ })
49
+ },
50
+
51
+ registerApi(ctx) {
52
+ ctx.addApiRegistration({
53
+ type: 'rpc-router',
54
+ name: 'cliDevice',
55
+ router: cliDeviceRouter,
56
+ })
57
+ },
58
+
59
+ async onBootstrap(ctx: KuckitModuleContext<CliAuthModuleConfig>) {
60
+ const { container } = ctx
61
+ const logger = container.resolve('logger')
62
+ logger.info('CLI Auth module initialized')
63
+ },
64
+
65
+ async onShutdown(ctx: KuckitModuleContext<CliAuthModuleConfig>) {
66
+ const { container } = ctx
67
+ const logger = container.resolve('logger')
68
+ logger.info('CLI Auth module shutting down')
69
+ },
70
+ })