@decido/shell-auth 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.
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@decido/shell-auth",
3
+ "version": "1.0.0",
4
+ "description": "Decido OS Shell Authentication & Obfuscated Persistence Core",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "directories": {
8
+ "src": "src"
9
+ },
10
+ "exports": {
11
+ ".": "./src/index.ts"
12
+ },
13
+ "dependencies": {
14
+ "idb-keyval": "^6.2.1",
15
+ "zustand": "^4.5.2",
16
+ "@decido/tauri-bridge": "0.1.0"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "license": "UNLICENSED",
25
+ "scripts": {
26
+ "lint": "eslint \"src/**/*.ts*\"",
27
+ "build": "tsup src/index.ts --format esm --dts --minify --clean"
28
+ }
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,131 @@
1
+ import { create } from 'zustand';
2
+ import { persist, createJSONStorage } from 'zustand/middleware';
3
+ import { secureStorage } from './secureStorage';
4
+
5
+ export type UserRole = 'creator' | 'admin' | 'user' | 'demo' | 'developer';
6
+ export type LicenseType = 'personal' | 'enterprise';
7
+
8
+ export interface LicenseTarget {
9
+ type: LicenseType;
10
+ id: string;
11
+ name: string;
12
+ enabledPlugins?: string[];
13
+ systemPrompt?: string;
14
+ swarmKey?: string;
15
+ websocketRelayUrl?: string;
16
+ }
17
+
18
+ export interface AuthState {
19
+ isAuthenticated: boolean;
20
+ userRole: UserRole | null;
21
+ userName: string | null;
22
+
23
+ availableLicenses: LicenseTarget[];
24
+ activeContext: LicenseTarget | null;
25
+ hasCompletedWelcome: boolean;
26
+ hasSeenTour: boolean;
27
+
28
+ login: (role: UserRole, name: string, licenses?: LicenseTarget[]) => void;
29
+ joinSwarm: (token: string) => Promise<boolean>;
30
+ setActiveContext: (licenseId: string) => void;
31
+ setHasCompletedWelcome: (val: boolean) => void;
32
+ setHasSeenTour: (val: boolean) => void;
33
+ logout: () => void;
34
+ }
35
+
36
+ export const useAuthStore = create<AuthState>()(
37
+ persist(
38
+ (set, get) => ({
39
+ isAuthenticated: false,
40
+ userRole: null,
41
+ userName: null,
42
+ availableLicenses: [],
43
+ activeContext: null,
44
+ hasCompletedWelcome: false,
45
+ hasSeenTour: false,
46
+
47
+ login: (role, name, licenses) => {
48
+ const defaultLicenses: LicenseTarget[] = licenses || [{
49
+ type: 'personal',
50
+ id: 'personal',
51
+ name: `${name}'s Private Space`
52
+ }];
53
+
54
+ set({
55
+ isAuthenticated: true,
56
+ userRole: role,
57
+ userName: name,
58
+ availableLicenses: defaultLicenses,
59
+ activeContext: defaultLicenses.length === 1 ? defaultLicenses[0] : null
60
+ });
61
+ },
62
+
63
+ joinSwarm: async (token: string) => {
64
+ try {
65
+ const base64Url = token.split('.')[1];
66
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
67
+ const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
68
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
69
+ }).join(''));
70
+
71
+ const payload = JSON.parse(jsonPayload);
72
+
73
+ if (!payload.tenantId || !payload.name) return false;
74
+
75
+ const newTarget: LicenseTarget = {
76
+ type: payload.type || 'enterprise',
77
+ id: payload.tenantId,
78
+ name: payload.name,
79
+ swarmKey: payload.swarm_key,
80
+ websocketRelayUrl: payload.ws_url
81
+ };
82
+
83
+ const { availableLicenses } = get();
84
+ if (!availableLicenses.find(l => l.id === newTarget.id)) {
85
+ set({
86
+ availableLicenses: [...availableLicenses, newTarget],
87
+ activeContext: newTarget
88
+ });
89
+ } else {
90
+ set({ activeContext: newTarget });
91
+ }
92
+ return true;
93
+ } catch (e) {
94
+ console.error("Failed to join swarm from token", e);
95
+ return false;
96
+ }
97
+ },
98
+
99
+ setActiveContext: (licenseId: string) => {
100
+ const { availableLicenses } = get();
101
+ const target = availableLicenses.find(l => l.id === licenseId) || null;
102
+ set({ activeContext: target });
103
+ },
104
+
105
+ setHasCompletedWelcome: (val: boolean) => {
106
+ set({ hasCompletedWelcome: val });
107
+ },
108
+
109
+ setHasSeenTour: (val: boolean) => {
110
+ set({ hasSeenTour: val });
111
+ },
112
+
113
+ logout: () => set({
114
+ isAuthenticated: false,
115
+ userRole: null,
116
+ userName: null,
117
+ availableLicenses: [],
118
+ activeContext: null,
119
+ hasCompletedWelcome: false,
120
+ hasSeenTour: false
121
+ }),
122
+ }),
123
+ {
124
+ name: 'decido-auth-storage',
125
+ storage: createJSONStorage(() => secureStorage)
126
+ }
127
+ )
128
+ );
129
+
130
+ // Re-export secureStorage helper just in case it's needed externally
131
+ export * from './secureStorage';
@@ -0,0 +1,61 @@
1
+ import { get, set, del } from 'idb-keyval';
2
+ import { StateStorage } from 'zustand/middleware';
3
+ import { secure } from '@decido/tauri-bridge';
4
+ import { isTauri } from '@decido/tauri-bridge';
5
+
6
+ // Basic obfuscation wrapper for pure SSR/Web Fallback
7
+ const obfuscate = (data: string): string => {
8
+ if (typeof btoa === 'undefined') return data;
9
+ return btoa(encodeURIComponent(data));
10
+ };
11
+
12
+ const deobfuscate = (data: string): string => {
13
+ if (typeof atob === 'undefined') return data;
14
+ try {
15
+ return decodeURIComponent(atob(data));
16
+ } catch {
17
+ return data;
18
+ }
19
+ };
20
+
21
+ export const secureStorage: StateStorage = {
22
+ getItem: async (name: string): Promise<string | null> => {
23
+ try {
24
+ if (isTauri()) {
25
+ const val = await secure.secureVault.getItem(name);
26
+ if (val !== null) return val;
27
+ }
28
+ // WEB Fallback
29
+ const val = await get(name);
30
+ if (!val) return null;
31
+ return deobfuscate(val);
32
+ } catch (err) {
33
+ console.error(`[SecureStorage] Failed to get item ${name}`, err);
34
+ return null;
35
+ }
36
+ },
37
+ setItem: async (name: string, value: string): Promise<void> => {
38
+ try {
39
+ if (isTauri()) {
40
+ await secure.secureVault.setItem(name, value);
41
+ return; // Do not leak to indexedDB
42
+ }
43
+ // WEB Fallback
44
+ const obfuscated = obfuscate(value);
45
+ await set(name, obfuscated);
46
+ } catch (err) {
47
+ console.error(`[SecureStorage] Failed to set item ${name}`, err);
48
+ }
49
+ },
50
+ removeItem: async (name: string): Promise<void> => {
51
+ try {
52
+ if (isTauri()) {
53
+ await secure.secureVault.removeItem(name);
54
+ // Also clean legacy web fallback just in case
55
+ }
56
+ await del(name);
57
+ } catch (err) {
58
+ console.error(`[SecureStorage] Failed to remove item ${name}`, err);
59
+ }
60
+ },
61
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "outDir": "dist",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
9
+ "module": "ESNext",
10
+ "target": "ESNext",
11
+ "moduleResolution": "bundler"
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }