@qidcloud/sdk 1.2.3 → 1.2.4

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.
@@ -1,64 +0,0 @@
1
- import React from 'react';
2
- import { QidCloud } from '../index';
3
- import { QidSignInButton } from './QidSignInButton';
4
- import { QidUser } from '../types';
5
-
6
- interface QidCloudLoginProps {
7
- sdk: QidCloud;
8
- onSuccess: (user: QidUser, token: string) => void;
9
- onError?: (error: string) => void;
10
- className?: string; // Wrapper class
11
- }
12
-
13
- /**
14
- * A comprehensive login component for QidCloud.
15
- * STRICT SECURITY: ONLY supports QR Scanning (Mobile App) for Post-Quantum security.
16
- * Conventional login is not available in the SDK to prevent password transmission in third-party apps.
17
- */
18
- export const QidCloudLogin: React.FC<QidCloudLoginProps> = ({
19
- sdk,
20
- onSuccess,
21
- onError,
22
- className
23
- }) => {
24
- return (
25
- <div className={`qid-login-container ${className || ''}`} style={{
26
- fontFamily: "'Inter', sans-serif",
27
- maxWidth: '380px',
28
- margin: '0 auto',
29
- padding: '30px',
30
- backgroundColor: '#050505',
31
- borderRadius: '24px',
32
- border: '1px solid #333',
33
- boxShadow: '0 20px 80px rgba(0,0,0,0.8)',
34
- color: '#fff',
35
- textAlign: 'center'
36
- }}>
37
- <div style={{ marginBottom: '30px' }}>
38
- <h2 style={{ fontSize: '1.5rem', fontWeight: 700, margin: '0 0 10px 0', letterSpacing: '-0.02em' }}>
39
- QidCloud Login
40
- </h2>
41
- <p style={{ color: '#888', fontSize: '0.9rem', margin: 0 }}>
42
- Secure Post-Quantum Access
43
- </p>
44
- </div>
45
-
46
- <div style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#111', borderRadius: '16px', border: '1px dashed #333' }}>
47
- <p style={{ fontSize: '0.85rem', color: '#888', marginBottom: '20px', lineHeight: '1.5' }}>
48
- Login instantly by scanning the secure QR code with your <strong>QidCloud App</strong>.
49
- </p>
50
- <QidSignInButton
51
- sdk={sdk}
52
- onSuccess={onSuccess}
53
- onError={onError}
54
- buttonText="Login with QIDCLOUD"
55
- className="qid-qr-btn-full"
56
- />
57
- </div>
58
-
59
- <p style={{ fontSize: '0.75rem', color: '#444', marginTop: '20px' }}>
60
- Credentials are never entered on this device.
61
- </p>
62
- </div>
63
- );
64
- };
@@ -1,260 +0,0 @@
1
- import React from 'react';
2
- import { QRCodeSVG } from 'qrcode.react';
3
- import { QidCloud } from '../index';
4
- import { QidUser } from '../types';
5
- import { useQidAuth } from '../hooks/useQidAuth';
6
-
7
- interface QidSignInButtonProps {
8
- sdk: QidCloud;
9
- onSuccess: (user: QidUser, token: string) => void;
10
- onError?: (error: string) => void;
11
- className?: string;
12
- buttonText?: string;
13
- }
14
-
15
- /**
16
- * A ready-to-use React component for QidCloud QR identity authentication.
17
- */
18
- export const QidSignInButton: React.FC<QidSignInButtonProps> = ({
19
- sdk,
20
- onSuccess,
21
- onError,
22
- className,
23
- buttonText = 'LOGIN WITH QIDCLOUD'
24
- }: QidSignInButtonProps) => {
25
- const {
26
- user,
27
- token,
28
- error,
29
- session,
30
- initializing,
31
- isExpired,
32
- timeLeft,
33
- login,
34
- cancel
35
- } = useQidAuth(sdk);
36
-
37
- const [appError, setAppError] = React.useState(false);
38
-
39
- // Watch for success
40
- React.useEffect(() => {
41
- if (user && token) {
42
- onSuccess(user, token);
43
- }
44
- }, [user, token, onSuccess]);
45
-
46
- // Watch for errors
47
- React.useEffect(() => {
48
- if (error && onError) {
49
- onError(error);
50
- }
51
- }, [error, onError]);
52
-
53
- return (
54
- <>
55
- <button
56
- onClick={login}
57
- disabled={initializing || !!session}
58
- className={className || 'qid-signin-btn'}
59
- style={{
60
- backgroundColor: '#00e5ff',
61
- color: '#000',
62
- padding: '12px 24px',
63
- borderRadius: '12px',
64
- border: 'none',
65
- fontWeight: '900',
66
- cursor: (initializing || !!session) ? 'not-allowed' : 'pointer',
67
- display: 'flex',
68
- alignItems: 'center',
69
- gap: '10px',
70
- textTransform: 'uppercase',
71
- fontSize: '0.85rem',
72
- letterSpacing: '0.5px',
73
- boxShadow: '0 4px 15px rgba(0, 229, 255, 0.3)',
74
- transition: 'all 0.2s'
75
- }}
76
- >
77
- <div style={{
78
- width: '8px',
79
- height: '8px',
80
- borderRadius: '50%',
81
- backgroundColor: (initializing || !!session) ? '#333' : '#000',
82
- boxShadow: (initializing || !!session) ? 'none' : '0 0 8px rgba(0,0,0,0.5)'
83
- }} />
84
- {initializing ? 'Preparing Handshake...' : (session ? 'Awaiting Scan...' : buttonText)}
85
- </button>
86
-
87
- {session && (
88
- <div className="qid-modal-overlay" style={{
89
- position: 'fixed',
90
- top: 0, left: 0, right: 0, bottom: 0,
91
- backgroundColor: 'rgba(0,0,0,0.92)',
92
- display: 'flex',
93
- justifyContent: 'center',
94
- alignItems: 'center',
95
- zIndex: 9999,
96
- backdropFilter: 'blur(8px)'
97
- }}>
98
- <div className="qid-modal-content" style={{
99
- backgroundColor: '#0a0a0a',
100
- padding: '40px',
101
- borderRadius: '28px',
102
- textAlign: 'center',
103
- color: '#fff',
104
- maxWidth: '420px',
105
- width: '90%',
106
- border: '1px solid rgba(0, 229, 255, 0.2)',
107
- boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)'
108
- }}>
109
- <div style={{ marginBottom: '25px' }}>
110
- <div style={{
111
- display: 'inline-block',
112
- padding: '12px 20px',
113
- borderRadius: '30px',
114
- backgroundColor: 'rgba(0, 229, 255, 0.1)',
115
- border: '1px solid rgba(0, 229, 255, 0.3)',
116
- marginBottom: '15px'
117
- }}>
118
- <span style={{ color: '#00e5ff', fontSize: '0.7rem', fontWeight: '900', letterSpacing: '2px' }}>PQC ENCLAVE SECURED</span>
119
- </div>
120
- <h2 style={{ fontSize: '1.75rem', fontWeight: '900', marginBottom: '10px', letterSpacing: '-0.5px' }}>Identity Handshake</h2>
121
- <p style={{ fontSize: '0.95rem', color: '#94a3b8', lineHeight: '1.6' }}>
122
- Scan this encrypted gateway with your QidCloud app to authorize access.
123
- </p>
124
- </div>
125
-
126
- <div style={{
127
- backgroundColor: '#fff',
128
- padding: '24px',
129
- display: 'inline-block',
130
- borderRadius: '24px',
131
- marginBottom: '30px',
132
- boxShadow: '0 10px 40px rgba(0,0,0,0.3)'
133
- }}>
134
- <QRCodeSVG
135
- value={session.qrData}
136
- size={220}
137
- level="H"
138
- includeMargin={false}
139
- style={{ opacity: isExpired ? 0.1 : 1, transition: 'opacity 0.3s' }}
140
- imageSettings={{
141
- src: "https://api.qidcloud.com/favicon.ico",
142
- x: undefined,
143
- y: undefined,
144
- height: 40,
145
- width: 40,
146
- excavate: true,
147
- }}
148
- />
149
- {isExpired && (
150
- <div style={{
151
- position: 'absolute',
152
- top: '50%',
153
- left: '50%',
154
- transform: 'translate(-50%, -50%)',
155
- width: '100%'
156
- }}>
157
- <button
158
- onClick={login}
159
- style={{
160
- backgroundColor: '#00e5ff',
161
- color: '#000',
162
- border: 'none',
163
- padding: '12px 20px',
164
- borderRadius: '12px',
165
- fontWeight: '900',
166
- cursor: 'pointer',
167
- boxShadow: '0 4px 15px rgba(0, 229, 255, 0.4)',
168
- fontSize: '0.8rem'
169
- }}
170
- >
171
- REFRESH QR
172
- </button>
173
- </div>
174
- )}
175
- </div>
176
-
177
- {isExpired && (
178
- <p style={{ color: '#ef4444', fontSize: '0.85rem', marginBottom: '20px', fontWeight: '600' }}>
179
- Session expired. Please refresh.
180
- </p>
181
- )}
182
-
183
- <div style={{ marginBottom: '20px' }}>
184
- <p style={{ fontSize: '0.85rem', color: '#94a3b8', marginBottom: '8px' }}>
185
- Are you on mobile?
186
- </p>
187
- <p style={{ fontSize: '0.75rem', color: '#64748b' }}>
188
- Use <strong>One-Click Auth</strong> for instant access.
189
- </p>
190
- </div>
191
-
192
- <div style={{ display: 'flex', gap: '12px', marginTop: '10px' }}>
193
- <button onClick={cancel} style={{
194
- flex: 2,
195
- padding: '16px',
196
- backgroundColor: 'transparent',
197
- color: '#64748b',
198
- border: '1px solid rgba(148, 163, 184, 0.2)',
199
- borderRadius: '16px',
200
- cursor: 'pointer',
201
- fontWeight: '700',
202
- transition: 'all 0.2s',
203
- fontSize: '0.9rem'
204
- }}>
205
- Cancel
206
- </button>
207
-
208
- {/* Mobile Deep Link Button */}
209
- <button
210
- onClick={() => {
211
- setAppError(false);
212
- const deepLink = `qidcloud://authorize?sessionId=${session.sessionId}`; // Corrected format per docs
213
-
214
- // Timeout to check if app opened
215
- const start = Date.now();
216
- window.location.href = deepLink;
217
-
218
- setTimeout(() => {
219
- if (Date.now() - start < 1500) {
220
- setAppError(true);
221
- }
222
- }, 1000);
223
- }}
224
- style={{
225
- flex: 1,
226
- padding: '16px',
227
- backgroundColor: 'rgba(0, 229, 255, 0.1)',
228
- color: '#00e5ff',
229
- border: '1px solid rgba(0, 229, 255, 0.3)',
230
- borderRadius: '16px',
231
- cursor: 'pointer',
232
- display: 'flex',
233
- alignItems: 'center',
234
- justifyContent: 'center',
235
- transition: 'all 0.2s'
236
- }}
237
- title="Authenticate with QidCloud App"
238
- >
239
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
240
- <rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
241
- <line x1="12" y1="18" x2="12.01" y2="18" />
242
- </svg>
243
- </button>
244
- </div>
245
-
246
- {appError && (
247
- <p style={{ color: '#f59e0b', fontSize: '0.75rem', marginTop: '12px', fontWeight: '600' }}>
248
- QidCloud App not found on device
249
- </p>
250
- )}
251
-
252
- <div style={{ marginTop: '25px', fontSize: '0.75rem', color: '#475569' }}>
253
- Session ID: {session.sessionId.slice(0, 8)}...
254
- </div>
255
- </div>
256
- </div>
257
- )}
258
- </>
259
- );
260
- };
@@ -1,179 +0,0 @@
1
- import { useState, useCallback, useEffect, useRef } from 'react';
2
- import { QidCloud } from '../index';
3
- import { QidUser, QidAuthSession } from '../types';
4
-
5
- export interface UseQidAuthReturn {
6
- user: QidUser | null;
7
- token: string | null;
8
- loading: boolean;
9
- error: string | null;
10
- session: QidAuthSession | null;
11
- initializing: boolean;
12
- isExpired: boolean;
13
- timeLeft: number;
14
- login: () => Promise<void>;
15
- logout: () => void;
16
- cancel: () => void;
17
- setAuthenticated: (user: QidUser, token: string) => void;
18
- }
19
-
20
- /**
21
- * A React hook for managing QidCloud authentication lifecycle.
22
- * Handles handshake initialization, WebSocket listeners, and profile fetching.
23
- */
24
- export function useQidAuth(sdk: QidCloud): UseQidAuthReturn {
25
- const [user, setUser] = useState<QidUser | null>(null);
26
- const [token, setToken] = useState<string | null>(null);
27
- const [session, setSession] = useState<QidAuthSession | null>(null);
28
- const [loading, setLoading] = useState(false);
29
- const [initializing, setInitializing] = useState(false);
30
- const [error, setError] = useState<string | null>(null);
31
-
32
- const [timeLeft, setTimeLeft] = useState<number>(0);
33
- const [isExpired, setIsExpired] = useState(false);
34
-
35
- // Use ref to track if we should still be listening (to avoid state updates after unmount/cancel)
36
- const activeSessionId = useRef<string | null>(null);
37
- const timerRef = useRef<NodeJS.Timeout | null>(null);
38
-
39
- const clearTimer = useCallback(() => {
40
- if (timerRef.current) {
41
- clearInterval(timerRef.current);
42
- timerRef.current = null;
43
- }
44
- }, []);
45
-
46
- const STORAGE_KEY = 'qid_auth_token';
47
-
48
- // Helper to fetch profile
49
- const fetchProfile = useCallback(async (authToken: string) => {
50
- setLoading(true);
51
- try {
52
- const profile = await sdk.auth.getProfile(authToken);
53
- setToken(authToken);
54
- setUser(profile);
55
- localStorage.setItem(STORAGE_KEY, authToken);
56
- } catch (err: any) {
57
- console.error('Failed to restore session:', err);
58
- setError('Session expired');
59
- localStorage.removeItem(STORAGE_KEY);
60
- setToken(null);
61
- setUser(null);
62
- } finally {
63
- setLoading(false);
64
- setInitializing(false);
65
- }
66
- }, [sdk]);
67
-
68
- // Init: Check local storage
69
- useEffect(() => {
70
- const storedToken = localStorage.getItem(STORAGE_KEY);
71
- if (storedToken) {
72
- fetchProfile(storedToken);
73
- }
74
- }, [fetchProfile]);
75
-
76
- const logout = useCallback(async () => {
77
- if (token) {
78
- await sdk.auth.logout(token);
79
- } else {
80
- sdk.auth.disconnect();
81
- }
82
- localStorage.removeItem(STORAGE_KEY);
83
- setUser(null);
84
- setToken(null);
85
- setSession(null);
86
- setIsExpired(false);
87
- clearTimer();
88
- }, [sdk, token, clearTimer]);
89
-
90
- const cancel = useCallback(() => {
91
- activeSessionId.current = null;
92
- setSession(null);
93
- setInitializing(false);
94
- setIsExpired(false);
95
- clearTimer();
96
- sdk.auth.disconnect();
97
- }, [sdk, clearTimer]);
98
-
99
- const login = useCallback(async () => {
100
- setInitializing(true);
101
- setError(null);
102
- setIsExpired(false);
103
- clearTimer();
104
-
105
- try {
106
- const newSession = await sdk.auth.createSession();
107
- setSession(newSession);
108
- activeSessionId.current = newSession.sessionId;
109
-
110
- // Start Timer
111
- const expiresAt = newSession.expiresAt || (Date.now() + 120000); // Fallback 2m
112
- setTimeLeft(Math.max(0, Math.floor((expiresAt - Date.now()) / 1000)));
113
-
114
- timerRef.current = setInterval(() => {
115
- const remaining = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
116
- setTimeLeft(remaining);
117
-
118
- if (remaining <= 0) {
119
- setIsExpired(true);
120
- clearTimer();
121
- // Don't nullify session immediately, let UI show expiry state
122
- }
123
- }, 1000);
124
-
125
- sdk.auth.listen(
126
- newSession.sessionId,
127
- async (receivedToken: string) => {
128
- // Safety check: is this still the active session?
129
- if (activeSessionId.current !== newSession.sessionId) return;
130
-
131
- clearTimer();
132
- setSession(null); // Clear QR session
133
-
134
- // Fetch profile and persist
135
- await fetchProfile(receivedToken);
136
- },
137
- (err: string) => {
138
- if (activeSessionId.current !== newSession.sessionId) return;
139
- setError(err);
140
- setInitializing(false);
141
- setSession(null);
142
- clearTimer();
143
- }
144
- );
145
- } catch (err: any) {
146
- setError(err.message || 'Failed to initiate login handshake');
147
- setInitializing(false);
148
- clearTimer();
149
- }
150
- }, [sdk, clearTimer, fetchProfile]);
151
-
152
- // Cleanup on unmount
153
- useEffect(() => {
154
- return () => {
155
- clearTimer();
156
- sdk.auth.disconnect();
157
- };
158
- }, [sdk, clearTimer]);
159
-
160
- return {
161
- user,
162
- token,
163
- loading,
164
- error,
165
- session,
166
- initializing,
167
- isExpired,
168
- timeLeft,
169
- login,
170
- logout,
171
- cancel,
172
- setAuthenticated: (user: QidUser, token: string) => {
173
- setUser(user);
174
- setToken(token);
175
- localStorage.setItem(STORAGE_KEY, token);
176
- setInitializing(false);
177
- }
178
- };
179
- }
package/src/index.ts DELETED
@@ -1,58 +0,0 @@
1
- import axios, { AxiosInstance } from 'axios';
2
- import { QidConfig } from './types';
3
- import { AuthModule } from './modules/auth';
4
- import { DbModule } from './modules/db';
5
- import { EdgeModule } from './modules/edge';
6
- import { VaultModule } from './modules/vault';
7
- import { LogsModule } from './modules/logs';
8
-
9
- export class QidCloud {
10
- private config: QidConfig;
11
- public api: AxiosInstance;
12
-
13
- public auth: AuthModule;
14
- public db: DbModule;
15
- public edge: EdgeModule;
16
- public vault: VaultModule;
17
- public logs: LogsModule;
18
-
19
- constructor(config: QidConfig) {
20
- this.config = {
21
- baseUrl: 'https://api.qidcloud.com',
22
- ...config,
23
- };
24
-
25
- this.api = axios.create({
26
- baseURL: this.config.baseUrl,
27
- headers: {
28
- 'x-api-key': this.config.apiKey,
29
- 'Content-Type': 'application/json',
30
- },
31
- });
32
-
33
- this.auth = new AuthModule(this);
34
- this.db = new DbModule(this);
35
- this.edge = new EdgeModule(this);
36
- this.vault = new VaultModule(this);
37
- this.logs = new LogsModule(this);
38
- }
39
-
40
- /**
41
- * Get the current project configuration
42
- */
43
- getConfig(): QidConfig {
44
- return { ...this.config };
45
- }
46
-
47
- // Modules will be initialized here
48
- // public auth = new AuthModule(this);
49
- // public db = new DbModule(this);
50
- // public storage = new StorageModule(this);
51
- // public edge = new EdgeModule(this);
52
- }
53
-
54
- export default QidCloud;
55
- export * from './types';
56
- export { QidSignInButton } from './components/QidSignInButton';
57
- export { QidCloudLogin } from './components/QidCloudLogin';
58
- export { useQidAuth } from './hooks/useQidAuth';
@@ -1,106 +0,0 @@
1
- import { io, Socket } from 'socket.io-client';
2
- import { QidCloud } from '../index';
3
- import { QidAuthSession, QidUser } from '../types';
4
-
5
- export class AuthModule {
6
- private sdk: QidCloud;
7
- private socket: Socket | null = null;
8
-
9
- constructor(sdk: QidCloud) {
10
- this.sdk = sdk;
11
- }
12
-
13
- /**
14
- * Initialize a new handshake session for QR login
15
- */
16
- async createSession(): Promise<QidAuthSession> {
17
- const resp = await this.sdk.api.post('/api/initiate-generic');
18
- const { sessionId, nonce } = resp.data;
19
-
20
- // Construct QR data (JSON format for mobile app)
21
- const qrData = JSON.stringify({
22
- sessionId,
23
- nonce,
24
- action: 'login-handshake',
25
- timestamp: Date.now()
26
- });
27
-
28
- return {
29
- sessionId,
30
- qrData,
31
- expiresAt: Date.now() + 120000 // 2 minutes
32
- };
33
- }
34
-
35
- /**
36
- * Listen for authorization events for a specific session
37
- */
38
- listen(sessionId: string, onAuthorized: (token: string) => void, onDenied?: (msg: string) => void) {
39
- if (!this.socket) {
40
- const baseUrl = this.sdk.getConfig().baseUrl || 'https://api.qidcloud.com';
41
- this.socket = io(baseUrl);
42
- }
43
-
44
- this.socket.emit('join', sessionId);
45
-
46
- this.socket.on('authenticated', (token: string) => {
47
- onAuthorized(token);
48
- });
49
-
50
- this.socket.on('identity_authorized', (data: any) => {
51
- // Some backend routes wrap the token in an object
52
- const token = typeof data === 'string' ? data : data.token;
53
- onAuthorized(token);
54
- });
55
-
56
- this.socket.on('identity_denied', (data: any) => {
57
- if (onDenied) onDenied(data.message || 'Authorization denied');
58
- });
59
- }
60
-
61
- /**
62
- * Terminate the session on the server
63
- */
64
- async logout(token: string): Promise<void> {
65
- if (!token) return;
66
- try {
67
- await this.sdk.api.post('/api/logout', {}, {
68
- headers: { 'Authorization': `Bearer ${token}` }
69
- });
70
- } catch (e) {
71
- console.warn('Logout failed or session already expired');
72
- } finally {
73
- this.disconnect();
74
- }
75
- }
76
-
77
- /**
78
- * Stop listening and disconnect socket
79
- */
80
- disconnect() {
81
- if (this.socket) {
82
- this.socket.disconnect();
83
- this.socket = null;
84
- }
85
- }
86
-
87
- /**
88
- * Fetch user profile using a session token
89
- */
90
- async getProfile(token: string): Promise<QidUser> {
91
- const resp = await this.sdk.api.get('/api/me', {
92
- headers: {
93
- 'Authorization': `Bearer ${token}`
94
- }
95
- });
96
- const data = resp.data;
97
- return {
98
- userId: data.regUserId,
99
- regUserId: data.regUserId,
100
- username: data.username,
101
- email: data.email,
102
- role: data.role
103
- };
104
- }
105
- }
106
-