@kavachos/electron 0.0.3
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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +213 -0
- package/dist/index.js +480 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KavachOS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ElectronStorageConfig {
|
|
5
|
+
/** File name for the session data. Defaults to "kavach-session.json". */
|
|
6
|
+
fileName?: string;
|
|
7
|
+
/** Optional additional encryption key applied on top of safeStorage. */
|
|
8
|
+
encryptionKey?: string;
|
|
9
|
+
}
|
|
10
|
+
interface SecureStorage {
|
|
11
|
+
get(key: string): Promise<string | null>;
|
|
12
|
+
set(key: string, value: string): Promise<void>;
|
|
13
|
+
remove(key: string): Promise<void>;
|
|
14
|
+
clear(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Creates a SecureStorage adapter backed by Electron's safeStorage API.
|
|
18
|
+
*
|
|
19
|
+
* Values are encrypted with safeStorage before being written to a JSON file
|
|
20
|
+
* in the app's userData directory. If safeStorage is unavailable (e.g. during
|
|
21
|
+
* testing or on headless Linux without a keychain), values are stored as
|
|
22
|
+
* plaintext and a warning is emitted once.
|
|
23
|
+
*/
|
|
24
|
+
declare function createElectronStorage(config?: ElectronStorageConfig): SecureStorage;
|
|
25
|
+
/**
|
|
26
|
+
* Creates a SecureStorage adapter that operates entirely in memory.
|
|
27
|
+
* Useful for testing or when you explicitly do not want disk persistence.
|
|
28
|
+
*/
|
|
29
|
+
declare function createMemoryStorage(): SecureStorage;
|
|
30
|
+
|
|
31
|
+
interface ElectronSafeStorage {
|
|
32
|
+
isEncryptionAvailable(): boolean;
|
|
33
|
+
encryptString(value: string): Uint8Array;
|
|
34
|
+
decryptString(encrypted: Uint8Array): string;
|
|
35
|
+
}
|
|
36
|
+
interface ElectronApp {
|
|
37
|
+
getPath(name: "userData"): string;
|
|
38
|
+
}
|
|
39
|
+
interface BrowserWindowOptions {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
webPreferences: {
|
|
43
|
+
nodeIntegration: boolean;
|
|
44
|
+
contextIsolation: boolean;
|
|
45
|
+
sandbox: boolean;
|
|
46
|
+
};
|
|
47
|
+
title: string;
|
|
48
|
+
resizable: boolean;
|
|
49
|
+
minimizable: boolean;
|
|
50
|
+
maximizable: boolean;
|
|
51
|
+
show: boolean;
|
|
52
|
+
}
|
|
53
|
+
interface BrowserWindowInstance {
|
|
54
|
+
loadURL(url: string): Promise<void>;
|
|
55
|
+
on(event: "closed", listener: () => void): void;
|
|
56
|
+
webContents: {
|
|
57
|
+
on(event: "will-redirect" | "will-navigate", listener: (event: unknown, url: string) => void): void;
|
|
58
|
+
session: {
|
|
59
|
+
cookies: {
|
|
60
|
+
get(filter: {
|
|
61
|
+
url: string;
|
|
62
|
+
}): Promise<Array<{
|
|
63
|
+
name: string;
|
|
64
|
+
value: string;
|
|
65
|
+
}>>;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
destroy(): void;
|
|
70
|
+
show(): void;
|
|
71
|
+
}
|
|
72
|
+
interface BrowserWindowConstructor {
|
|
73
|
+
new (options: BrowserWindowOptions): BrowserWindowInstance;
|
|
74
|
+
}
|
|
75
|
+
type IpcMainListener = (event: {
|
|
76
|
+
reply(channel: string, ...args: unknown[]): void;
|
|
77
|
+
}, ...args: unknown[]) => Promise<unknown>;
|
|
78
|
+
interface ElectronIpcMain {
|
|
79
|
+
handle(channel: string, listener: IpcMainListener): void;
|
|
80
|
+
removeHandler(channel: string): void;
|
|
81
|
+
}
|
|
82
|
+
interface ElectronIpcRenderer {
|
|
83
|
+
invoke(channel: string, ...args: unknown[]): Promise<unknown>;
|
|
84
|
+
}
|
|
85
|
+
interface ElectronApi {
|
|
86
|
+
safeStorage: ElectronSafeStorage;
|
|
87
|
+
app: ElectronApp;
|
|
88
|
+
BrowserWindow: BrowserWindowConstructor;
|
|
89
|
+
ipcMain: ElectronIpcMain;
|
|
90
|
+
ipcRenderer: ElectronIpcRenderer;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* For testing only — injects a mock Electron API so modules don't need a real
|
|
94
|
+
* Electron runtime. Call setElectronApiForTesting(null) to reset.
|
|
95
|
+
*/
|
|
96
|
+
declare function setElectronApiForTesting(mock: ElectronApi | null): void;
|
|
97
|
+
|
|
98
|
+
interface OAuthWindowConfig {
|
|
99
|
+
/** Window width in pixels. Defaults to 500. */
|
|
100
|
+
width?: number;
|
|
101
|
+
/** Window height in pixels. Defaults to 700. */
|
|
102
|
+
height?: number;
|
|
103
|
+
/** Base path where KavachOS is mounted. Defaults to "/api/kavach". */
|
|
104
|
+
basePath?: string;
|
|
105
|
+
}
|
|
106
|
+
interface OAuthWindowResult {
|
|
107
|
+
success: boolean;
|
|
108
|
+
error?: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Opens a BrowserWindow to the OAuth authorization URL for a given provider,
|
|
112
|
+
* waits for the callback redirect, extracts the session cookie, then closes
|
|
113
|
+
* the window.
|
|
114
|
+
*
|
|
115
|
+
* Must be called from the Electron main process.
|
|
116
|
+
*/
|
|
117
|
+
declare function openOAuthWindow(providerId: string, config?: OAuthWindowConfig): Promise<OAuthWindowResult>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Registers IPC handlers in the main process so the renderer can securely
|
|
121
|
+
* access storage without needing direct Node.js access.
|
|
122
|
+
*
|
|
123
|
+
* Call this once from your main process entry point after creating your
|
|
124
|
+
* SecureStorage instance via createElectronStorage().
|
|
125
|
+
*/
|
|
126
|
+
declare function setupKavachIpc(storage: SecureStorage): void;
|
|
127
|
+
/**
|
|
128
|
+
* Creates a SecureStorage implementation that delegates all operations to the
|
|
129
|
+
* main process via IPC. Use this in the renderer process in place of a direct
|
|
130
|
+
* storage adapter so that Node.js APIs and encryption stay in the main process.
|
|
131
|
+
*
|
|
132
|
+
* Requires contextIsolation to be enabled and the IPC channels to be exposed
|
|
133
|
+
* via a preload script, or for contextIsolation to be disabled (not recommended).
|
|
134
|
+
*/
|
|
135
|
+
declare function createIpcStorage(): SecureStorage;
|
|
136
|
+
/**
|
|
137
|
+
* The IPC channel names exposed for use in a preload script with contextBridge.
|
|
138
|
+
* If you use contextIsolation (recommended), expose these channels explicitly:
|
|
139
|
+
*
|
|
140
|
+
* ```ts
|
|
141
|
+
* import { contextBridge, ipcRenderer } from "electron";
|
|
142
|
+
* import { KAVACH_IPC_CHANNELS } from "@kavachos/electron";
|
|
143
|
+
*
|
|
144
|
+
* contextBridge.exposeInMainWorld("kavachStorage", {
|
|
145
|
+
* invoke: (channel: string, ...args: unknown[]) => {
|
|
146
|
+
* if (!Object.values(KAVACH_IPC_CHANNELS).includes(channel)) {
|
|
147
|
+
* throw new Error(`Blocked IPC channel: ${channel}`);
|
|
148
|
+
* }
|
|
149
|
+
* return ipcRenderer.invoke(channel, ...args);
|
|
150
|
+
* },
|
|
151
|
+
* });
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
declare const KAVACH_IPC_CHANNELS: {
|
|
155
|
+
readonly GET: "kavach:storage:get";
|
|
156
|
+
readonly SET: "kavach:storage:set";
|
|
157
|
+
readonly REMOVE: "kavach:storage:remove";
|
|
158
|
+
readonly CLEAR: "kavach:storage:clear";
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
interface KavachUser {
|
|
162
|
+
id: string;
|
|
163
|
+
email?: string;
|
|
164
|
+
name?: string;
|
|
165
|
+
image?: string;
|
|
166
|
+
}
|
|
167
|
+
interface KavachSession {
|
|
168
|
+
token: string;
|
|
169
|
+
user: KavachUser;
|
|
170
|
+
expiresAt?: string;
|
|
171
|
+
}
|
|
172
|
+
type ActionResult<T = void> = {
|
|
173
|
+
success: true;
|
|
174
|
+
data: T;
|
|
175
|
+
} | {
|
|
176
|
+
success: false;
|
|
177
|
+
error: string;
|
|
178
|
+
};
|
|
179
|
+
interface KavachContextValue {
|
|
180
|
+
session: KavachSession | null;
|
|
181
|
+
user: KavachUser | null;
|
|
182
|
+
isLoading: boolean;
|
|
183
|
+
isAuthenticated: boolean;
|
|
184
|
+
signIn: (email: string, password: string) => Promise<ActionResult>;
|
|
185
|
+
signUp: (email: string, password: string, name?: string) => Promise<ActionResult>;
|
|
186
|
+
signOut: () => Promise<void>;
|
|
187
|
+
refresh: () => Promise<void>;
|
|
188
|
+
}
|
|
189
|
+
interface ElectronKavachProviderProps {
|
|
190
|
+
children: ReactNode;
|
|
191
|
+
/** Base path where KavachOS is mounted. Defaults to "/api/kavach". */
|
|
192
|
+
basePath?: string;
|
|
193
|
+
/** Custom storage adapter. Defaults to in-memory if not provided. */
|
|
194
|
+
storage?: SecureStorage;
|
|
195
|
+
/** Persist session to secure storage across restarts. Defaults to true. */
|
|
196
|
+
persistSession?: boolean;
|
|
197
|
+
}
|
|
198
|
+
declare const ElectronKavachContext: react.Context<KavachContextValue | null>;
|
|
199
|
+
declare function useElectronKavachContext(): KavachContextValue;
|
|
200
|
+
/**
|
|
201
|
+
* Wraps your Electron renderer with KavachOS auth state.
|
|
202
|
+
*
|
|
203
|
+
* Extends the base KavachProvider behaviour with:
|
|
204
|
+
* - Session persistence to Electron's safeStorage on sign-in
|
|
205
|
+
* - Session restoration from storage on app launch
|
|
206
|
+
* - Storage cleared on sign-out
|
|
207
|
+
*
|
|
208
|
+
* Pass a SecureStorage created by createElectronStorage() or createIpcStorage()
|
|
209
|
+
* depending on whether your renderer runs with Node integration or via IPC.
|
|
210
|
+
*/
|
|
211
|
+
declare function ElectronKavachProvider({ children, basePath, storage, persistSession, }: ElectronKavachProviderProps): ReactNode;
|
|
212
|
+
|
|
213
|
+
export { type ElectronApi, ElectronKavachContext, ElectronKavachProvider, type ElectronKavachProviderProps, type ElectronStorageConfig, KAVACH_IPC_CHANNELS, type OAuthWindowConfig, type OAuthWindowResult, type SecureStorage, createElectronStorage, createIpcStorage, createMemoryStorage, openOAuthWindow, setElectronApiForTesting, setupKavachIpc, useElectronKavachContext };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/electron-api.ts
|
|
9
|
+
var _override = null;
|
|
10
|
+
function loadElectron() {
|
|
11
|
+
if (_override) return _override;
|
|
12
|
+
try {
|
|
13
|
+
return __require("electron");
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function getElectronApi() {
|
|
19
|
+
const api = loadElectron();
|
|
20
|
+
if (!api) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"[KavachOS] Electron API not available. Ensure this code runs in an Electron process."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return api;
|
|
26
|
+
}
|
|
27
|
+
function setElectronApiForTesting(mock) {
|
|
28
|
+
_override = mock;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/storage.ts
|
|
32
|
+
import * as fs from "fs";
|
|
33
|
+
import * as path from "path";
|
|
34
|
+
function xorWithKey(data, key) {
|
|
35
|
+
if (key.length === 0) return data;
|
|
36
|
+
const encoder = new TextEncoder();
|
|
37
|
+
const keyBytes = encoder.encode(key);
|
|
38
|
+
const result = new Uint8Array(data.length);
|
|
39
|
+
for (let i = 0; i < data.length; i++) {
|
|
40
|
+
result[i] = (data[i] ?? 0) ^ (keyBytes[i % keyBytes.length] ?? 0);
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
function uint8ArrayToBase64(bytes) {
|
|
45
|
+
let binary = "";
|
|
46
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
47
|
+
binary += String.fromCharCode(bytes[i] ?? 0);
|
|
48
|
+
}
|
|
49
|
+
return btoa(binary);
|
|
50
|
+
}
|
|
51
|
+
function base64ToUint8Array(base64) {
|
|
52
|
+
const binary = atob(base64);
|
|
53
|
+
const bytes = new Uint8Array(binary.length);
|
|
54
|
+
for (let i = 0; i < binary.length; i++) {
|
|
55
|
+
bytes[i] = binary.charCodeAt(i);
|
|
56
|
+
}
|
|
57
|
+
return bytes;
|
|
58
|
+
}
|
|
59
|
+
function readStorageFile(filePath) {
|
|
60
|
+
try {
|
|
61
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
62
|
+
const parsed = JSON.parse(raw);
|
|
63
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
return {};
|
|
67
|
+
} catch {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function writeStorageFile(filePath, data) {
|
|
72
|
+
const dir = path.dirname(filePath);
|
|
73
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
74
|
+
fs.writeFileSync(filePath, JSON.stringify(data), "utf8");
|
|
75
|
+
}
|
|
76
|
+
function createElectronStorage(config) {
|
|
77
|
+
const fileName = config?.fileName ?? "kavach-session.json";
|
|
78
|
+
const encryptionKey = config?.encryptionKey ?? "";
|
|
79
|
+
let warned = false;
|
|
80
|
+
let filePath = null;
|
|
81
|
+
function getFilePath() {
|
|
82
|
+
if (filePath) return filePath;
|
|
83
|
+
const electron = getElectronApi();
|
|
84
|
+
const resolved = path.join(electron.app.getPath("userData"), fileName);
|
|
85
|
+
filePath = resolved;
|
|
86
|
+
return resolved;
|
|
87
|
+
}
|
|
88
|
+
function getSafeStorage() {
|
|
89
|
+
const electron = getElectronApi();
|
|
90
|
+
if (!electron.safeStorage.isEncryptionAvailable()) {
|
|
91
|
+
if (!warned) {
|
|
92
|
+
warned = true;
|
|
93
|
+
console.warn(
|
|
94
|
+
"KavachOS: safeStorage encryption unavailable \u2014 storing session data in plaintext"
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return electron.safeStorage;
|
|
100
|
+
}
|
|
101
|
+
function encryptValue(value) {
|
|
102
|
+
const safeStorage = getSafeStorage();
|
|
103
|
+
if (!safeStorage) return value;
|
|
104
|
+
const encrypted = safeStorage.encryptString(value);
|
|
105
|
+
const keyed = encryptionKey ? xorWithKey(encrypted, encryptionKey) : encrypted;
|
|
106
|
+
return uint8ArrayToBase64(keyed);
|
|
107
|
+
}
|
|
108
|
+
function decryptValue(stored) {
|
|
109
|
+
const safeStorage = getSafeStorage();
|
|
110
|
+
if (!safeStorage) return stored;
|
|
111
|
+
try {
|
|
112
|
+
let bytes = base64ToUint8Array(stored);
|
|
113
|
+
if (encryptionKey) {
|
|
114
|
+
bytes = xorWithKey(bytes, encryptionKey);
|
|
115
|
+
}
|
|
116
|
+
return safeStorage.decryptString(bytes);
|
|
117
|
+
} catch {
|
|
118
|
+
return stored;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
async get(key) {
|
|
123
|
+
const map = readStorageFile(getFilePath());
|
|
124
|
+
const stored = map[key];
|
|
125
|
+
if (stored === void 0) return null;
|
|
126
|
+
return decryptValue(stored);
|
|
127
|
+
},
|
|
128
|
+
async set(key, value) {
|
|
129
|
+
const fp = getFilePath();
|
|
130
|
+
const map = readStorageFile(fp);
|
|
131
|
+
map[key] = encryptValue(value);
|
|
132
|
+
writeStorageFile(fp, map);
|
|
133
|
+
},
|
|
134
|
+
async remove(key) {
|
|
135
|
+
const fp = getFilePath();
|
|
136
|
+
const map = readStorageFile(fp);
|
|
137
|
+
const next = {};
|
|
138
|
+
for (const [k, v] of Object.entries(map)) {
|
|
139
|
+
if (k !== key) next[k] = v;
|
|
140
|
+
}
|
|
141
|
+
writeStorageFile(fp, next);
|
|
142
|
+
},
|
|
143
|
+
async clear() {
|
|
144
|
+
const fp = getFilePath();
|
|
145
|
+
writeStorageFile(fp, {});
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function createMemoryStorage() {
|
|
150
|
+
const store = /* @__PURE__ */ new Map();
|
|
151
|
+
return {
|
|
152
|
+
async get(key) {
|
|
153
|
+
return store.get(key) ?? null;
|
|
154
|
+
},
|
|
155
|
+
async set(key, value) {
|
|
156
|
+
store.set(key, value);
|
|
157
|
+
},
|
|
158
|
+
async remove(key) {
|
|
159
|
+
store.delete(key);
|
|
160
|
+
},
|
|
161
|
+
async clear() {
|
|
162
|
+
store.clear();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/oauth-window.ts
|
|
168
|
+
var SESSION_COOKIE_NAMES = ["kavach-session", "better-auth.session_token", "__session"];
|
|
169
|
+
async function extractSessionCookie(win, baseUrl) {
|
|
170
|
+
const cookies = await win.webContents.session.cookies.get({ url: baseUrl });
|
|
171
|
+
for (const name of SESSION_COOKIE_NAMES) {
|
|
172
|
+
const cookie = cookies.find((c) => c.name === name);
|
|
173
|
+
if (cookie) return cookie.value;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
function isCallbackUrl(url, base) {
|
|
178
|
+
const normalised = base.replace(/\/$/, "");
|
|
179
|
+
return url.includes(`${normalised}/callback`) || url.includes(`${normalised}/oauth/callback`) || url.includes("code=") || url.includes("error=");
|
|
180
|
+
}
|
|
181
|
+
function extractOAuthError(url) {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = new URL(url, "http://localhost");
|
|
184
|
+
return parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error");
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function openOAuthWindow(providerId, config = {}) {
|
|
190
|
+
const { width = 500, height = 700, basePath = "/api/kavach" } = config;
|
|
191
|
+
const base = basePath.replace(/\/$/, "");
|
|
192
|
+
const { BrowserWindow } = getElectronApi();
|
|
193
|
+
const win = new BrowserWindow({
|
|
194
|
+
width,
|
|
195
|
+
height,
|
|
196
|
+
webPreferences: {
|
|
197
|
+
nodeIntegration: false,
|
|
198
|
+
contextIsolation: true,
|
|
199
|
+
sandbox: true
|
|
200
|
+
},
|
|
201
|
+
title: "Sign in",
|
|
202
|
+
resizable: false,
|
|
203
|
+
minimizable: false,
|
|
204
|
+
maximizable: false,
|
|
205
|
+
show: false
|
|
206
|
+
});
|
|
207
|
+
const authUrl = `${base}/sign-in/${encodeURIComponent(providerId)}?redirect=electron`;
|
|
208
|
+
return new Promise((resolve) => {
|
|
209
|
+
let settled = false;
|
|
210
|
+
function settle(result) {
|
|
211
|
+
if (settled) return;
|
|
212
|
+
settled = true;
|
|
213
|
+
win.destroy();
|
|
214
|
+
resolve(result);
|
|
215
|
+
}
|
|
216
|
+
win.on("closed", () => {
|
|
217
|
+
settle({ success: false, error: "Window closed by user" });
|
|
218
|
+
});
|
|
219
|
+
async function handleNavigate(_event, url) {
|
|
220
|
+
if (!isCallbackUrl(url, base)) return;
|
|
221
|
+
const oauthError = extractOAuthError(url);
|
|
222
|
+
if (oauthError) {
|
|
223
|
+
settle({ success: false, error: oauthError });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const origin = new URL(url).origin;
|
|
228
|
+
const cookie = await extractSessionCookie(win, origin);
|
|
229
|
+
void cookie;
|
|
230
|
+
settle({ success: true });
|
|
231
|
+
} catch (err) {
|
|
232
|
+
settle({
|
|
233
|
+
success: false,
|
|
234
|
+
error: err instanceof Error ? err.message : "Failed to extract session"
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
win.webContents.on("will-redirect", (ev, url) => {
|
|
239
|
+
void handleNavigate(ev, url);
|
|
240
|
+
});
|
|
241
|
+
win.webContents.on("will-navigate", (ev, url) => {
|
|
242
|
+
void handleNavigate(ev, url);
|
|
243
|
+
});
|
|
244
|
+
void win.loadURL(authUrl).then(() => {
|
|
245
|
+
win.show();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/ipc.ts
|
|
251
|
+
var CHANNEL_GET = "kavach:storage:get";
|
|
252
|
+
var CHANNEL_SET = "kavach:storage:set";
|
|
253
|
+
var CHANNEL_REMOVE = "kavach:storage:remove";
|
|
254
|
+
var CHANNEL_CLEAR = "kavach:storage:clear";
|
|
255
|
+
function setupKavachIpc(storage) {
|
|
256
|
+
const { ipcMain } = getElectronApi();
|
|
257
|
+
ipcMain.removeHandler(CHANNEL_GET);
|
|
258
|
+
ipcMain.removeHandler(CHANNEL_SET);
|
|
259
|
+
ipcMain.removeHandler(CHANNEL_REMOVE);
|
|
260
|
+
ipcMain.removeHandler(CHANNEL_CLEAR);
|
|
261
|
+
ipcMain.handle(CHANNEL_GET, async (_event, req) => {
|
|
262
|
+
const { key } = req;
|
|
263
|
+
const value = await storage.get(key);
|
|
264
|
+
return { value };
|
|
265
|
+
});
|
|
266
|
+
ipcMain.handle(CHANNEL_SET, async (_event, req) => {
|
|
267
|
+
const { key, value } = req;
|
|
268
|
+
await storage.set(key, value);
|
|
269
|
+
});
|
|
270
|
+
ipcMain.handle(CHANNEL_REMOVE, async (_event, req) => {
|
|
271
|
+
const { key } = req;
|
|
272
|
+
await storage.remove(key);
|
|
273
|
+
});
|
|
274
|
+
ipcMain.handle(CHANNEL_CLEAR, async () => {
|
|
275
|
+
await storage.clear();
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function createIpcStorage() {
|
|
279
|
+
const { ipcRenderer } = getElectronApi();
|
|
280
|
+
return {
|
|
281
|
+
async get(key) {
|
|
282
|
+
const req = { key };
|
|
283
|
+
const res = await ipcRenderer.invoke(CHANNEL_GET, req);
|
|
284
|
+
return res.value;
|
|
285
|
+
},
|
|
286
|
+
async set(key, value) {
|
|
287
|
+
const req = { key, value };
|
|
288
|
+
await ipcRenderer.invoke(CHANNEL_SET, req);
|
|
289
|
+
},
|
|
290
|
+
async remove(key) {
|
|
291
|
+
const req = { key };
|
|
292
|
+
await ipcRenderer.invoke(CHANNEL_REMOVE, req);
|
|
293
|
+
},
|
|
294
|
+
async clear() {
|
|
295
|
+
await ipcRenderer.invoke(CHANNEL_CLEAR);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
var KAVACH_IPC_CHANNELS = {
|
|
300
|
+
GET: CHANNEL_GET,
|
|
301
|
+
SET: CHANNEL_SET,
|
|
302
|
+
REMOVE: CHANNEL_REMOVE,
|
|
303
|
+
CLEAR: CHANNEL_CLEAR
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// src/provider.tsx
|
|
307
|
+
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
308
|
+
import { jsx } from "react/jsx-runtime";
|
|
309
|
+
var SESSION_STORAGE_KEY = "kavach:session";
|
|
310
|
+
var ElectronKavachContext = createContext(null);
|
|
311
|
+
function useElectronKavachContext() {
|
|
312
|
+
const ctx = useContext(ElectronKavachContext);
|
|
313
|
+
if (!ctx) {
|
|
314
|
+
throw new Error("useElectronKavachContext must be used inside <ElectronKavachProvider>");
|
|
315
|
+
}
|
|
316
|
+
return ctx;
|
|
317
|
+
}
|
|
318
|
+
function extractErrorMessage(body, fallback) {
|
|
319
|
+
if (body !== null && typeof body === "object" && "error" in body && typeof body.error === "object" && typeof body.error?.message === "string") {
|
|
320
|
+
return body.error?.message ?? fallback;
|
|
321
|
+
}
|
|
322
|
+
return fallback;
|
|
323
|
+
}
|
|
324
|
+
function ElectronKavachProvider({
|
|
325
|
+
children,
|
|
326
|
+
basePath = "/api/kavach",
|
|
327
|
+
storage,
|
|
328
|
+
persistSession = true
|
|
329
|
+
}) {
|
|
330
|
+
const [session, setSession] = useState(null);
|
|
331
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
332
|
+
const base = basePath.replace(/\/$/, "");
|
|
333
|
+
const persistToStorage = useCallback(
|
|
334
|
+
async (s) => {
|
|
335
|
+
if (!storage || !persistSession) return;
|
|
336
|
+
if (s === null) {
|
|
337
|
+
await storage.remove(SESSION_STORAGE_KEY);
|
|
338
|
+
} else {
|
|
339
|
+
await storage.set(SESSION_STORAGE_KEY, JSON.stringify(s));
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
[storage, persistSession]
|
|
343
|
+
);
|
|
344
|
+
const restoreFromStorage = useCallback(async () => {
|
|
345
|
+
if (!storage || !persistSession) return null;
|
|
346
|
+
try {
|
|
347
|
+
const raw = await storage.get(SESSION_STORAGE_KEY);
|
|
348
|
+
if (!raw) return null;
|
|
349
|
+
const parsed = JSON.parse(raw);
|
|
350
|
+
if (parsed !== null && typeof parsed === "object" && "token" in parsed && typeof parsed.token === "string" && "user" in parsed && typeof parsed.user === "object") {
|
|
351
|
+
return parsed;
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
} catch {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}, [storage, persistSession]);
|
|
358
|
+
const fetchSession = useCallback(async () => {
|
|
359
|
+
try {
|
|
360
|
+
const res = await fetch(`${base}/session`, { credentials: "include" });
|
|
361
|
+
if (res.ok) {
|
|
362
|
+
const json = await res.json();
|
|
363
|
+
const s = json.data ?? null;
|
|
364
|
+
setSession(s);
|
|
365
|
+
await persistToStorage(s);
|
|
366
|
+
} else {
|
|
367
|
+
setSession(null);
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
setSession(null);
|
|
371
|
+
}
|
|
372
|
+
}, [base, persistToStorage]);
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
setIsLoading(true);
|
|
375
|
+
void (async () => {
|
|
376
|
+
const stored = await restoreFromStorage();
|
|
377
|
+
if (stored) {
|
|
378
|
+
setSession(stored);
|
|
379
|
+
}
|
|
380
|
+
await fetchSession();
|
|
381
|
+
setIsLoading(false);
|
|
382
|
+
})();
|
|
383
|
+
}, [restoreFromStorage, fetchSession]);
|
|
384
|
+
const refresh = useCallback(async () => {
|
|
385
|
+
await fetchSession();
|
|
386
|
+
}, [fetchSession]);
|
|
387
|
+
const signIn = useCallback(
|
|
388
|
+
async (email, password) => {
|
|
389
|
+
try {
|
|
390
|
+
const res = await fetch(`${base}/sign-in/email`, {
|
|
391
|
+
method: "POST",
|
|
392
|
+
credentials: "include",
|
|
393
|
+
headers: { "Content-Type": "application/json" },
|
|
394
|
+
body: JSON.stringify({ email, password })
|
|
395
|
+
});
|
|
396
|
+
const json = await res.json();
|
|
397
|
+
if (!res.ok) {
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
error: extractErrorMessage(json, `Sign-in failed (${res.status})`)
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const s = json.data;
|
|
404
|
+
setSession(s);
|
|
405
|
+
await persistToStorage(s);
|
|
406
|
+
return { success: true, data: void 0 };
|
|
407
|
+
} catch (err) {
|
|
408
|
+
return {
|
|
409
|
+
success: false,
|
|
410
|
+
error: err instanceof Error ? err.message : "Network error"
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
[base, persistToStorage]
|
|
415
|
+
);
|
|
416
|
+
const signUp = useCallback(
|
|
417
|
+
async (email, password, name) => {
|
|
418
|
+
try {
|
|
419
|
+
const res = await fetch(`${base}/sign-up/email`, {
|
|
420
|
+
method: "POST",
|
|
421
|
+
credentials: "include",
|
|
422
|
+
headers: { "Content-Type": "application/json" },
|
|
423
|
+
body: JSON.stringify({ email, password, name })
|
|
424
|
+
});
|
|
425
|
+
const json = await res.json();
|
|
426
|
+
if (!res.ok) {
|
|
427
|
+
return {
|
|
428
|
+
success: false,
|
|
429
|
+
error: extractErrorMessage(json, `Sign-up failed (${res.status})`)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const s = json.data;
|
|
433
|
+
setSession(s);
|
|
434
|
+
await persistToStorage(s);
|
|
435
|
+
return { success: true, data: void 0 };
|
|
436
|
+
} catch (err) {
|
|
437
|
+
return {
|
|
438
|
+
success: false,
|
|
439
|
+
error: err instanceof Error ? err.message : "Network error"
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
[base, persistToStorage]
|
|
444
|
+
);
|
|
445
|
+
const signOut = useCallback(async () => {
|
|
446
|
+
try {
|
|
447
|
+
await fetch(`${base}/sign-out`, {
|
|
448
|
+
method: "POST",
|
|
449
|
+
credentials: "include"
|
|
450
|
+
});
|
|
451
|
+
} finally {
|
|
452
|
+
setSession(null);
|
|
453
|
+
await persistToStorage(null);
|
|
454
|
+
}
|
|
455
|
+
}, [base, persistToStorage]);
|
|
456
|
+
const value = {
|
|
457
|
+
session,
|
|
458
|
+
user: session?.user ?? null,
|
|
459
|
+
isLoading,
|
|
460
|
+
isAuthenticated: session !== null,
|
|
461
|
+
signIn,
|
|
462
|
+
signUp,
|
|
463
|
+
signOut,
|
|
464
|
+
refresh
|
|
465
|
+
};
|
|
466
|
+
return /* @__PURE__ */ jsx(ElectronKavachContext.Provider, { value, children });
|
|
467
|
+
}
|
|
468
|
+
export {
|
|
469
|
+
ElectronKavachContext,
|
|
470
|
+
ElectronKavachProvider,
|
|
471
|
+
KAVACH_IPC_CHANNELS,
|
|
472
|
+
createElectronStorage,
|
|
473
|
+
createIpcStorage,
|
|
474
|
+
createMemoryStorage,
|
|
475
|
+
openOAuthWindow,
|
|
476
|
+
setElectronApiForTesting,
|
|
477
|
+
setupKavachIpc,
|
|
478
|
+
useElectronKavachContext
|
|
479
|
+
};
|
|
480
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/electron-api.ts","../src/storage.ts","../src/oauth-window.ts","../src/ipc.ts","../src/provider.tsx"],"sourcesContent":["// ─── Electron API injection ───────────────────────────────────────────────────\n//\n// Modules that need Electron APIs call getElectronApi() to retrieve the live\n// bindings. In a real Electron process this resolves to the real electron\n// module; in tests it resolves to the mock set via setElectronApiForTesting().\n\nimport type { SecureStorage } from \"./storage.js\";\n\n// ─── Surface interfaces ───────────────────────────────────────────────────────\n\nexport interface ElectronSafeStorage {\n\tisEncryptionAvailable(): boolean;\n\tencryptString(value: string): Uint8Array;\n\tdecryptString(encrypted: Uint8Array): string;\n}\n\nexport interface ElectronApp {\n\tgetPath(name: \"userData\"): string;\n}\n\nexport interface BrowserWindowOptions {\n\twidth: number;\n\theight: number;\n\twebPreferences: {\n\t\tnodeIntegration: boolean;\n\t\tcontextIsolation: boolean;\n\t\tsandbox: boolean;\n\t};\n\ttitle: string;\n\tresizable: boolean;\n\tminimizable: boolean;\n\tmaximizable: boolean;\n\tshow: boolean;\n}\n\nexport interface BrowserWindowInstance {\n\tloadURL(url: string): Promise<void>;\n\ton(event: \"closed\", listener: () => void): void;\n\twebContents: {\n\t\ton(\n\t\t\tevent: \"will-redirect\" | \"will-navigate\",\n\t\t\tlistener: (event: unknown, url: string) => void,\n\t\t): void;\n\t\tsession: {\n\t\t\tcookies: {\n\t\t\t\tget(filter: { url: string }): Promise<Array<{ name: string; value: string }>>;\n\t\t\t};\n\t\t};\n\t};\n\tdestroy(): void;\n\tshow(): void;\n}\n\nexport interface BrowserWindowConstructor {\n\tnew (options: BrowserWindowOptions): BrowserWindowInstance;\n}\n\nexport type IpcMainListener = (\n\tevent: { reply(channel: string, ...args: unknown[]): void },\n\t...args: unknown[]\n) => Promise<unknown>;\n\nexport interface ElectronIpcMain {\n\thandle(channel: string, listener: IpcMainListener): void;\n\tremoveHandler(channel: string): void;\n}\n\nexport interface ElectronIpcRenderer {\n\tinvoke(channel: string, ...args: unknown[]): Promise<unknown>;\n}\n\nexport interface ElectronApi {\n\tsafeStorage: ElectronSafeStorage;\n\tapp: ElectronApp;\n\tBrowserWindow: BrowserWindowConstructor;\n\tipcMain: ElectronIpcMain;\n\tipcRenderer: ElectronIpcRenderer;\n}\n\n// ─── Singleton ────────────────────────────────────────────────────────────────\n\nlet _override: ElectronApi | null = null;\n\nfunction loadElectron(): ElectronApi | null {\n\tif (_override) return _override;\n\ttry {\n\t\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\t\treturn require(\"electron\") as ElectronApi;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function getElectronApi(): ElectronApi {\n\tconst api = loadElectron();\n\tif (!api) {\n\t\tthrow new Error(\n\t\t\t\"[KavachOS] Electron API not available. Ensure this code runs in an Electron process.\",\n\t\t);\n\t}\n\treturn api;\n}\n\n/**\n * For testing only — injects a mock Electron API so modules don't need a real\n * Electron runtime. Call setElectronApiForTesting(null) to reset.\n */\nexport function setElectronApiForTesting(mock: ElectronApi | null): void {\n\t_override = mock;\n}\n\n// Re-export SecureStorage so ipc.ts can reference it without a circular dep\nexport type { SecureStorage };\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { ElectronSafeStorage } from \"./electron-api.js\";\nimport { getElectronApi } from \"./electron-api.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface ElectronStorageConfig {\n\t/** File name for the session data. Defaults to \"kavach-session.json\". */\n\tfileName?: string;\n\t/** Optional additional encryption key applied on top of safeStorage. */\n\tencryptionKey?: string;\n}\n\nexport interface SecureStorage {\n\tget(key: string): Promise<string | null>;\n\tset(key: string, value: string): Promise<void>;\n\tremove(key: string): Promise<void>;\n\tclear(): Promise<void>;\n}\n\n// ─── XOR cipher for optional encryptionKey ────────────────────────────────────\n\nfunction xorWithKey(data: Uint8Array, key: string): Uint8Array {\n\tif (key.length === 0) return data;\n\tconst encoder = new TextEncoder();\n\tconst keyBytes = encoder.encode(key);\n\tconst result = new Uint8Array(data.length);\n\tfor (let i = 0; i < data.length; i++) {\n\t\tresult[i] = (data[i] ?? 0) ^ (keyBytes[i % keyBytes.length] ?? 0);\n\t}\n\treturn result;\n}\n\n// ─── Base64 helpers (no Buffer) ───────────────────────────────────────────────\n\nfunction uint8ArrayToBase64(bytes: Uint8Array): string {\n\tlet binary = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i] ?? 0);\n\t}\n\treturn btoa(binary);\n}\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n\tconst binary = atob(base64);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n}\n\n// ─── Storage file helpers ─────────────────────────────────────────────────────\n\ntype StorageMap = Record<string, string>;\n\nfunction readStorageFile(filePath: string): StorageMap {\n\ttry {\n\t\tconst raw = fs.readFileSync(filePath, \"utf8\");\n\t\tconst parsed: unknown = JSON.parse(raw);\n\t\tif (parsed !== null && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n\t\t\treturn parsed as StorageMap;\n\t\t}\n\t\treturn {};\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nfunction writeStorageFile(filePath: string, data: StorageMap): void {\n\tconst dir = path.dirname(filePath);\n\tfs.mkdirSync(dir, { recursive: true });\n\tfs.writeFileSync(filePath, JSON.stringify(data), \"utf8\");\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\n/**\n * Creates a SecureStorage adapter backed by Electron's safeStorage API.\n *\n * Values are encrypted with safeStorage before being written to a JSON file\n * in the app's userData directory. If safeStorage is unavailable (e.g. during\n * testing or on headless Linux without a keychain), values are stored as\n * plaintext and a warning is emitted once.\n */\nexport function createElectronStorage(config?: ElectronStorageConfig): SecureStorage {\n\tconst fileName = config?.fileName ?? \"kavach-session.json\";\n\tconst encryptionKey = config?.encryptionKey ?? \"\";\n\n\tlet warned = false;\n\tlet filePath: string | null = null;\n\n\tfunction getFilePath(): string {\n\t\tif (filePath) return filePath;\n\t\tconst electron = getElectronApi();\n\t\tconst resolved = path.join(electron.app.getPath(\"userData\"), fileName);\n\t\tfilePath = resolved;\n\t\treturn resolved;\n\t}\n\n\tfunction getSafeStorage(): ElectronSafeStorage | null {\n\t\tconst electron = getElectronApi();\n\t\tif (!electron.safeStorage.isEncryptionAvailable()) {\n\t\t\tif (!warned) {\n\t\t\t\twarned = true;\n\t\t\t\t// biome-ignore lint/suspicious/noConsole: intentional user-facing warning\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"KavachOS: safeStorage encryption unavailable — storing session data in plaintext\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t\treturn electron.safeStorage;\n\t}\n\n\tfunction encryptValue(value: string): string {\n\t\tconst safeStorage = getSafeStorage();\n\t\tif (!safeStorage) return value;\n\t\tconst encrypted = safeStorage.encryptString(value);\n\t\tconst keyed = encryptionKey ? xorWithKey(encrypted, encryptionKey) : encrypted;\n\t\treturn uint8ArrayToBase64(keyed);\n\t}\n\n\tfunction decryptValue(stored: string): string {\n\t\tconst safeStorage = getSafeStorage();\n\t\tif (!safeStorage) return stored;\n\t\ttry {\n\t\t\tlet bytes = base64ToUint8Array(stored);\n\t\t\tif (encryptionKey) {\n\t\t\t\tbytes = xorWithKey(bytes, encryptionKey);\n\t\t\t}\n\t\t\treturn safeStorage.decryptString(bytes);\n\t\t} catch {\n\t\t\treturn stored;\n\t\t}\n\t}\n\n\treturn {\n\t\tasync get(key: string): Promise<string | null> {\n\t\t\tconst map = readStorageFile(getFilePath());\n\t\t\tconst stored = map[key];\n\t\t\tif (stored === undefined) return null;\n\t\t\treturn decryptValue(stored);\n\t\t},\n\n\t\tasync set(key: string, value: string): Promise<void> {\n\t\t\tconst fp = getFilePath();\n\t\t\tconst map = readStorageFile(fp);\n\t\t\tmap[key] = encryptValue(value);\n\t\t\twriteStorageFile(fp, map);\n\t\t},\n\n\t\tasync remove(key: string): Promise<void> {\n\t\t\tconst fp = getFilePath();\n\t\t\tconst map = readStorageFile(fp);\n\t\t\t// Functional deletion without `delete` operator\n\t\t\tconst next: StorageMap = {};\n\t\t\tfor (const [k, v] of Object.entries(map)) {\n\t\t\t\tif (k !== key) next[k] = v;\n\t\t\t}\n\t\t\twriteStorageFile(fp, next);\n\t\t},\n\n\t\tasync clear(): Promise<void> {\n\t\t\tconst fp = getFilePath();\n\t\t\twriteStorageFile(fp, {});\n\t\t},\n\t};\n}\n\n/**\n * Creates a SecureStorage adapter that operates entirely in memory.\n * Useful for testing or when you explicitly do not want disk persistence.\n */\nexport function createMemoryStorage(): SecureStorage {\n\tconst store = new Map<string, string>();\n\treturn {\n\t\tasync get(key: string): Promise<string | null> {\n\t\t\treturn store.get(key) ?? null;\n\t\t},\n\t\tasync set(key: string, value: string): Promise<void> {\n\t\t\tstore.set(key, value);\n\t\t},\n\t\tasync remove(key: string): Promise<void> {\n\t\t\tstore.delete(key);\n\t\t},\n\t\tasync clear(): Promise<void> {\n\t\t\tstore.clear();\n\t\t},\n\t};\n}\n","import type { BrowserWindowInstance } from \"./electron-api.js\";\nimport { getElectronApi } from \"./electron-api.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface OAuthWindowConfig {\n\t/** Window width in pixels. Defaults to 500. */\n\twidth?: number;\n\t/** Window height in pixels. Defaults to 700. */\n\theight?: number;\n\t/** Base path where KavachOS is mounted. Defaults to \"/api/kavach\". */\n\tbasePath?: string;\n}\n\nexport interface OAuthWindowResult {\n\tsuccess: boolean;\n\terror?: string;\n}\n\n// ─── Session cookie extraction ────────────────────────────────────────────────\n\nconst SESSION_COOKIE_NAMES = [\"kavach-session\", \"better-auth.session_token\", \"__session\"];\n\nasync function extractSessionCookie(\n\twin: BrowserWindowInstance,\n\tbaseUrl: string,\n): Promise<string | null> {\n\tconst cookies = await win.webContents.session.cookies.get({ url: baseUrl });\n\tfor (const name of SESSION_COOKIE_NAMES) {\n\t\tconst cookie = cookies.find((c) => c.name === name);\n\t\tif (cookie) return cookie.value;\n\t}\n\treturn null;\n}\n\n// ─── URL parsing helpers ──────────────────────────────────────────────────────\n\nfunction isCallbackUrl(url: string, base: string): boolean {\n\tconst normalised = base.replace(/\\/$/, \"\");\n\treturn (\n\t\turl.includes(`${normalised}/callback`) ||\n\t\turl.includes(`${normalised}/oauth/callback`) ||\n\t\turl.includes(\"code=\") ||\n\t\turl.includes(\"error=\")\n\t);\n}\n\nfunction extractOAuthError(url: string): string | null {\n\ttry {\n\t\t// Use a dummy base so relative paths are accepted\n\t\tconst parsed = new URL(url, \"http://localhost\");\n\t\treturn parsed.searchParams.get(\"error_description\") ?? parsed.searchParams.get(\"error\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// ─── Main export ──────────────────────────────────────────────────────────────\n\n/**\n * Opens a BrowserWindow to the OAuth authorization URL for a given provider,\n * waits for the callback redirect, extracts the session cookie, then closes\n * the window.\n *\n * Must be called from the Electron main process.\n */\nexport async function openOAuthWindow(\n\tproviderId: string,\n\tconfig: OAuthWindowConfig = {},\n): Promise<OAuthWindowResult> {\n\tconst { width = 500, height = 700, basePath = \"/api/kavach\" } = config;\n\tconst base = basePath.replace(/\\/$/, \"\");\n\n\tconst { BrowserWindow } = getElectronApi();\n\n\tconst win = new BrowserWindow({\n\t\twidth,\n\t\theight,\n\t\twebPreferences: {\n\t\t\tnodeIntegration: false,\n\t\t\tcontextIsolation: true,\n\t\t\tsandbox: true,\n\t\t},\n\t\ttitle: \"Sign in\",\n\t\tresizable: false,\n\t\tminimizable: false,\n\t\tmaximizable: false,\n\t\tshow: false,\n\t});\n\n\tconst authUrl = `${base}/sign-in/${encodeURIComponent(providerId)}?redirect=electron`;\n\n\treturn new Promise<OAuthWindowResult>((resolve) => {\n\t\tlet settled = false;\n\n\t\tfunction settle(result: OAuthWindowResult): void {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\twin.destroy();\n\t\t\tresolve(result);\n\t\t}\n\n\t\twin.on(\"closed\", () => {\n\t\t\tsettle({ success: false, error: \"Window closed by user\" });\n\t\t});\n\n\t\tasync function handleNavigate(_event: unknown, url: string): Promise<void> {\n\t\t\tif (!isCallbackUrl(url, base)) return;\n\n\t\t\tconst oauthError = extractOAuthError(url);\n\t\t\tif (oauthError) {\n\t\t\t\tsettle({ success: false, error: oauthError });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst origin = new URL(url).origin;\n\t\t\t\tconst cookie = await extractSessionCookie(win, origin);\n\t\t\t\t// Treat as success whether or not we captured the cookie — the\n\t\t\t\t// session will be verified on the next server round-trip.\n\t\t\t\tvoid cookie;\n\t\t\t\tsettle({ success: true });\n\t\t\t} catch (err) {\n\t\t\t\tsettle({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: err instanceof Error ? err.message : \"Failed to extract session\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\twin.webContents.on(\"will-redirect\", (ev, url) => {\n\t\t\tvoid handleNavigate(ev, url);\n\t\t});\n\t\twin.webContents.on(\"will-navigate\", (ev, url) => {\n\t\t\tvoid handleNavigate(ev, url);\n\t\t});\n\n\t\tvoid win.loadURL(authUrl).then(() => {\n\t\t\twin.show();\n\t\t});\n\t});\n}\n","import { getElectronApi } from \"./electron-api.js\";\nimport type { SecureStorage } from \"./storage.js\";\n\n// ─── IPC channel names ────────────────────────────────────────────────────────\n\nconst CHANNEL_GET = \"kavach:storage:get\";\nconst CHANNEL_SET = \"kavach:storage:set\";\nconst CHANNEL_REMOVE = \"kavach:storage:remove\";\nconst CHANNEL_CLEAR = \"kavach:storage:clear\";\n\n// ─── Request/response types ───────────────────────────────────────────────────\n\ninterface StorageGetRequest {\n\tkey: string;\n}\n\ninterface StorageSetRequest {\n\tkey: string;\n\tvalue: string;\n}\n\ninterface StorageRemoveRequest {\n\tkey: string;\n}\n\ninterface StorageGetResponse {\n\tvalue: string | null;\n}\n\n// ─── Main process side ────────────────────────────────────────────────────────\n\n/**\n * Registers IPC handlers in the main process so the renderer can securely\n * access storage without needing direct Node.js access.\n *\n * Call this once from your main process entry point after creating your\n * SecureStorage instance via createElectronStorage().\n */\nexport function setupKavachIpc(storage: SecureStorage): void {\n\tconst { ipcMain } = getElectronApi();\n\n\t// Remove any previously registered handlers to allow re-initialization.\n\tipcMain.removeHandler(CHANNEL_GET);\n\tipcMain.removeHandler(CHANNEL_SET);\n\tipcMain.removeHandler(CHANNEL_REMOVE);\n\tipcMain.removeHandler(CHANNEL_CLEAR);\n\n\tipcMain.handle(CHANNEL_GET, async (_event, req: unknown): Promise<StorageGetResponse> => {\n\t\tconst { key } = req as StorageGetRequest;\n\t\tconst value = await storage.get(key);\n\t\treturn { value };\n\t});\n\n\tipcMain.handle(CHANNEL_SET, async (_event, req: unknown): Promise<void> => {\n\t\tconst { key, value } = req as StorageSetRequest;\n\t\tawait storage.set(key, value);\n\t});\n\n\tipcMain.handle(CHANNEL_REMOVE, async (_event, req: unknown): Promise<void> => {\n\t\tconst { key } = req as StorageRemoveRequest;\n\t\tawait storage.remove(key);\n\t});\n\n\tipcMain.handle(CHANNEL_CLEAR, async (): Promise<void> => {\n\t\tawait storage.clear();\n\t});\n}\n\n// ─── Renderer process side ────────────────────────────────────────────────────\n\n/**\n * Creates a SecureStorage implementation that delegates all operations to the\n * main process via IPC. Use this in the renderer process in place of a direct\n * storage adapter so that Node.js APIs and encryption stay in the main process.\n *\n * Requires contextIsolation to be enabled and the IPC channels to be exposed\n * via a preload script, or for contextIsolation to be disabled (not recommended).\n */\nexport function createIpcStorage(): SecureStorage {\n\tconst { ipcRenderer } = getElectronApi();\n\n\treturn {\n\t\tasync get(key: string): Promise<string | null> {\n\t\t\tconst req: StorageGetRequest = { key };\n\t\t\tconst res = (await ipcRenderer.invoke(CHANNEL_GET, req)) as StorageGetResponse;\n\t\t\treturn res.value;\n\t\t},\n\n\t\tasync set(key: string, value: string): Promise<void> {\n\t\t\tconst req: StorageSetRequest = { key, value };\n\t\t\tawait ipcRenderer.invoke(CHANNEL_SET, req);\n\t\t},\n\n\t\tasync remove(key: string): Promise<void> {\n\t\t\tconst req: StorageRemoveRequest = { key };\n\t\t\tawait ipcRenderer.invoke(CHANNEL_REMOVE, req);\n\t\t},\n\n\t\tasync clear(): Promise<void> {\n\t\t\tawait ipcRenderer.invoke(CHANNEL_CLEAR);\n\t\t},\n\t};\n}\n\n// ─── Preload bridge helper ────────────────────────────────────────────────────\n\n/**\n * The IPC channel names exposed for use in a preload script with contextBridge.\n * If you use contextIsolation (recommended), expose these channels explicitly:\n *\n * ```ts\n * import { contextBridge, ipcRenderer } from \"electron\";\n * import { KAVACH_IPC_CHANNELS } from \"@kavachos/electron\";\n *\n * contextBridge.exposeInMainWorld(\"kavachStorage\", {\n * invoke: (channel: string, ...args: unknown[]) => {\n * if (!Object.values(KAVACH_IPC_CHANNELS).includes(channel)) {\n * throw new Error(`Blocked IPC channel: ${channel}`);\n * }\n * return ipcRenderer.invoke(channel, ...args);\n * },\n * });\n * ```\n */\nexport const KAVACH_IPC_CHANNELS = {\n\tGET: CHANNEL_GET,\n\tSET: CHANNEL_SET,\n\tREMOVE: CHANNEL_REMOVE,\n\tCLEAR: CHANNEL_CLEAR,\n} as const;\n","import type { ReactNode } from \"react\";\nimport { createContext, useCallback, useContext, useEffect, useState } from \"react\";\nimport type { SecureStorage } from \"./storage.js\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ninterface KavachUser {\n\tid: string;\n\temail?: string;\n\tname?: string;\n\timage?: string;\n}\n\ninterface KavachSession {\n\ttoken: string;\n\tuser: KavachUser;\n\texpiresAt?: string;\n}\n\ntype ActionResult<T = void> = { success: true; data: T } | { success: false; error: string };\n\ninterface KavachContextValue {\n\tsession: KavachSession | null;\n\tuser: KavachUser | null;\n\tisLoading: boolean;\n\tisAuthenticated: boolean;\n\tsignIn: (email: string, password: string) => Promise<ActionResult>;\n\tsignUp: (email: string, password: string, name?: string) => Promise<ActionResult>;\n\tsignOut: () => Promise<void>;\n\trefresh: () => Promise<void>;\n}\n\nexport interface ElectronKavachProviderProps {\n\tchildren: ReactNode;\n\t/** Base path where KavachOS is mounted. Defaults to \"/api/kavach\". */\n\tbasePath?: string;\n\t/** Custom storage adapter. Defaults to in-memory if not provided. */\n\tstorage?: SecureStorage;\n\t/** Persist session to secure storage across restarts. Defaults to true. */\n\tpersistSession?: boolean;\n}\n\n// ─── Storage key ──────────────────────────────────────────────────────────────\n\nconst SESSION_STORAGE_KEY = \"kavach:session\";\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport const ElectronKavachContext = createContext<KavachContextValue | null>(null);\n\nexport function useElectronKavachContext(): KavachContextValue {\n\tconst ctx = useContext(ElectronKavachContext);\n\tif (!ctx) {\n\t\tthrow new Error(\"useElectronKavachContext must be used inside <ElectronKavachProvider>\");\n\t}\n\treturn ctx;\n}\n\n// ─── JSON fetch helpers ───────────────────────────────────────────────────────\n\ntype JsonErrorBody = { error?: { code?: string; message?: string } };\n\nfunction extractErrorMessage(body: unknown, fallback: string): string {\n\tif (\n\t\tbody !== null &&\n\t\ttypeof body === \"object\" &&\n\t\t\"error\" in body &&\n\t\ttypeof (body as JsonErrorBody).error === \"object\" &&\n\t\ttypeof (body as JsonErrorBody).error?.message === \"string\"\n\t) {\n\t\treturn (body as JsonErrorBody).error?.message ?? fallback;\n\t}\n\treturn fallback;\n}\n\n// ─── Provider ─────────────────────────────────────────────────────────────────\n\n/**\n * Wraps your Electron renderer with KavachOS auth state.\n *\n * Extends the base KavachProvider behaviour with:\n * - Session persistence to Electron's safeStorage on sign-in\n * - Session restoration from storage on app launch\n * - Storage cleared on sign-out\n *\n * Pass a SecureStorage created by createElectronStorage() or createIpcStorage()\n * depending on whether your renderer runs with Node integration or via IPC.\n */\nexport function ElectronKavachProvider({\n\tchildren,\n\tbasePath = \"/api/kavach\",\n\tstorage,\n\tpersistSession = true,\n}: ElectronKavachProviderProps): ReactNode {\n\tconst [session, setSession] = useState<KavachSession | null>(null);\n\tconst [isLoading, setIsLoading] = useState(true);\n\n\tconst base = basePath.replace(/\\/$/, \"\");\n\n\t// ── Persist / restore helpers ───────────────────────────────────────────\n\n\tconst persistToStorage = useCallback(\n\t\tasync (s: KavachSession | null): Promise<void> => {\n\t\t\tif (!storage || !persistSession) return;\n\t\t\tif (s === null) {\n\t\t\t\tawait storage.remove(SESSION_STORAGE_KEY);\n\t\t\t} else {\n\t\t\t\tawait storage.set(SESSION_STORAGE_KEY, JSON.stringify(s));\n\t\t\t}\n\t\t},\n\t\t[storage, persistSession],\n\t);\n\n\tconst restoreFromStorage = useCallback(async (): Promise<KavachSession | null> => {\n\t\tif (!storage || !persistSession) return null;\n\t\ttry {\n\t\t\tconst raw = await storage.get(SESSION_STORAGE_KEY);\n\t\t\tif (!raw) return null;\n\t\t\tconst parsed: unknown = JSON.parse(raw);\n\t\t\tif (\n\t\t\t\tparsed !== null &&\n\t\t\t\ttypeof parsed === \"object\" &&\n\t\t\t\t\"token\" in parsed &&\n\t\t\t\ttypeof (parsed as KavachSession).token === \"string\" &&\n\t\t\t\t\"user\" in parsed &&\n\t\t\t\ttypeof (parsed as KavachSession).user === \"object\"\n\t\t\t) {\n\t\t\t\treturn parsed as KavachSession;\n\t\t\t}\n\t\t\treturn null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}, [storage, persistSession]);\n\n\t// ── Session fetch ───────────────────────────────────────────────────────\n\n\tconst fetchSession = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tconst res = await fetch(`${base}/session`, { credentials: \"include\" });\n\t\t\tif (res.ok) {\n\t\t\t\tconst json = (await res.json()) as { data?: KavachSession };\n\t\t\t\tconst s = json.data ?? null;\n\t\t\t\tsetSession(s);\n\t\t\t\tawait persistToStorage(s);\n\t\t\t} else {\n\t\t\t\tsetSession(null);\n\t\t\t}\n\t\t} catch {\n\t\t\tsetSession(null);\n\t\t}\n\t}, [base, persistToStorage]);\n\n\t// ── Mount: restore from storage, then verify with server ───────────────\n\n\tuseEffect(() => {\n\t\tsetIsLoading(true);\n\n\t\tvoid (async () => {\n\t\t\tconst stored = await restoreFromStorage();\n\t\t\tif (stored) {\n\t\t\t\tsetSession(stored);\n\t\t\t}\n\t\t\t// Always verify the session with the server regardless of storage.\n\t\t\tawait fetchSession();\n\t\t\tsetIsLoading(false);\n\t\t})();\n\t}, [restoreFromStorage, fetchSession]);\n\n\t// ── Auth actions ────────────────────────────────────────────────────────\n\n\tconst refresh = useCallback(async (): Promise<void> => {\n\t\tawait fetchSession();\n\t}, [fetchSession]);\n\n\tconst signIn = useCallback(\n\t\tasync (email: string, password: string): Promise<ActionResult> => {\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(`${base}/sign-in/email`, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\tbody: JSON.stringify({ email, password }),\n\t\t\t\t});\n\t\t\t\tconst json = (await res.json()) as\n\t\t\t\t\t| { data: KavachSession }\n\t\t\t\t\t| { error: { code: string; message: string } };\n\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\terror: extractErrorMessage(json, `Sign-in failed (${res.status})`),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst s = (json as { data: KavachSession }).data;\n\t\t\t\tsetSession(s);\n\t\t\t\tawait persistToStorage(s);\n\t\t\t\treturn { success: true, data: undefined };\n\t\t\t} catch (err) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: err instanceof Error ? err.message : \"Network error\",\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t\t[base, persistToStorage],\n\t);\n\n\tconst signUp = useCallback(\n\t\tasync (email: string, password: string, name?: string): Promise<ActionResult> => {\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(`${base}/sign-up/email`, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\tbody: JSON.stringify({ email, password, name }),\n\t\t\t\t});\n\t\t\t\tconst json = (await res.json()) as\n\t\t\t\t\t| { data: KavachSession }\n\t\t\t\t\t| { error: { code: string; message: string } };\n\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\terror: extractErrorMessage(json, `Sign-up failed (${res.status})`),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst s = (json as { data: KavachSession }).data;\n\t\t\t\tsetSession(s);\n\t\t\t\tawait persistToStorage(s);\n\t\t\t\treturn { success: true, data: undefined };\n\t\t\t} catch (err) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: err instanceof Error ? err.message : \"Network error\",\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t\t[base, persistToStorage],\n\t);\n\n\tconst signOut = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tawait fetch(`${base}/sign-out`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tcredentials: \"include\",\n\t\t\t});\n\t\t} finally {\n\t\t\tsetSession(null);\n\t\t\tawait persistToStorage(null);\n\t\t}\n\t}, [base, persistToStorage]);\n\n\t// ── Context value ───────────────────────────────────────────────────────\n\n\tconst value: KavachContextValue = {\n\t\tsession,\n\t\tuser: session?.user ?? null,\n\t\tisLoading,\n\t\tisAuthenticated: session !== null,\n\t\tsignIn,\n\t\tsignUp,\n\t\tsignOut,\n\t\trefresh,\n\t};\n\n\treturn <ElectronKavachContext.Provider value={value}>{children}</ElectronKavachContext.Provider>;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kavachos/electron",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "KavachOS auth client for Electron desktop apps",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "KavachOS <hello@kavachos.com>",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/kavachos/kavachos.git",
|
|
22
|
+
"directory": "packages/electron"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"electron": ">=28.0.0",
|
|
26
|
+
"react": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"@types/react": "^19.0.0",
|
|
31
|
+
"electron": "^33.0.0",
|
|
32
|
+
"react": "^19.0.0",
|
|
33
|
+
"tsup": "^8.4.0",
|
|
34
|
+
"typescript": "^5.8.0",
|
|
35
|
+
"vitest": "^2.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"test": "vitest run"
|
|
41
|
+
}
|
|
42
|
+
}
|