@tgtone/auth-sdk 1.4.2 → 1.4.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.
- package/CHANGELOG.md +72 -0
- package/MIGRATION_GUIDE.md +293 -0
- package/README.md +17 -52
- package/dist/tgtone-auth-client.d.ts +1 -0
- package/dist/tgtone-auth-client.d.ts.map +1 -1
- package/dist/tgtone-auth-client.js +4 -1
- package/dist/tgtone-auth-client.js.map +1 -1
- package/docs/API.md +844 -0
- package/docs/INTEGRATION_EXAMPLES.md +181 -322
- package/docs/{NPM_PUBLISH.md → PUBLISHING.md} +1 -1
- package/docs/QUICKSTART_REACT.md +295 -0
- package/docs/README.md +33 -23
- package/package.json +3 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/docs/LOVABLE_QUICK_START.md +0 -316
|
@@ -1,177 +1,131 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Ejemplos de Integración por Framework
|
|
2
2
|
|
|
3
|
-
Guía paso a paso para integrar TGT One Auth en diferentes frameworks.
|
|
3
|
+
Guía paso a paso para integrar TGT One Auth en diferentes frameworks usando el paquete npm.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## React (Vite)
|
|
8
8
|
|
|
9
|
-
###
|
|
9
|
+
### 1. Instalar
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
|
|
13
|
-
mkdir -p src/lib/auth
|
|
14
|
-
cp tgtone-auth-client.ts src/lib/auth/
|
|
15
|
-
cp react-hook.tsx src/lib/auth/
|
|
12
|
+
npm install @tgtone/auth-sdk
|
|
16
13
|
```
|
|
17
14
|
|
|
18
|
-
###
|
|
15
|
+
### 2. Usar el hook `useTGTAuth`
|
|
19
16
|
|
|
20
17
|
```tsx
|
|
21
|
-
// src/
|
|
22
|
-
import {
|
|
23
|
-
import { useTGTAuth, UseTGTAuthResult } from '@/lib/auth/react-hook';
|
|
24
|
-
|
|
25
|
-
const AuthContext = createContext<UseTGTAuthResult | null>(null);
|
|
18
|
+
// src/App.tsx
|
|
19
|
+
import { useTGTAuth } from '@tgtone/auth-sdk/react';
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
identityUrl: 'https://identity.tgtone.cl',
|
|
30
|
-
appDomain:
|
|
31
|
-
|
|
21
|
+
function App() {
|
|
22
|
+
const { session, loading, logout, hasRole, revokedError } = useTGTAuth({
|
|
23
|
+
identityUrl: import.meta.env.VITE_IDENTITY_URL || 'https://identity.tgtone.cl',
|
|
24
|
+
appDomain: window.location.host,
|
|
25
|
+
appKey: import.meta.env.VITE_APP_KEY, // Requerido en dev
|
|
26
|
+
enableHeartbeat: true,
|
|
27
|
+
debug: import.meta.env.DEV,
|
|
32
28
|
});
|
|
33
29
|
|
|
30
|
+
if (loading) return <LoadingSpinner />;
|
|
31
|
+
if (revokedError) return <RevokedPage error={revokedError} />;
|
|
32
|
+
if (!session) return null;
|
|
33
|
+
|
|
34
34
|
return (
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
<div>
|
|
36
|
+
<header>
|
|
37
|
+
<span>{session.user.name} - {session.tenantName}</span>
|
|
38
|
+
<button onClick={logout}>Cerrar sesión</button>
|
|
39
|
+
</header>
|
|
40
|
+
{hasRole('zenith', 'admin') && <AdminPanel />}
|
|
41
|
+
</div>
|
|
38
42
|
);
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
export const useAuth = () => {
|
|
42
|
-
const context = useContext(AuthContext);
|
|
43
|
-
if (!context) {
|
|
44
|
-
throw new Error('useAuth debe usarse dentro de AuthProvider');
|
|
45
|
-
}
|
|
46
|
-
return context;
|
|
47
|
-
};
|
|
48
44
|
```
|
|
49
45
|
|
|
50
|
-
###
|
|
46
|
+
### 3. Interceptor Axios para API calls
|
|
51
47
|
|
|
52
48
|
```tsx
|
|
53
|
-
|
|
54
|
-
import {
|
|
55
|
-
import { createRoot } from 'react-dom/client';
|
|
56
|
-
import { AuthProvider } from './contexts/AuthContext';
|
|
57
|
-
import App from './App';
|
|
58
|
-
|
|
59
|
-
createRoot(document.getElementById('root')!).render(
|
|
60
|
-
<StrictMode>
|
|
61
|
-
<AuthProvider>
|
|
62
|
-
<App />
|
|
63
|
-
</AuthProvider>
|
|
64
|
-
</StrictMode>,
|
|
65
|
-
);
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### **4. Usar en componentes**
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
// src/App.tsx
|
|
72
|
-
import { useAuth } from './contexts/AuthContext';
|
|
73
|
-
|
|
74
|
-
function App() {
|
|
75
|
-
const { user, loading, logout, hasRole } = useAuth();
|
|
76
|
-
|
|
77
|
-
if (loading) {
|
|
78
|
-
return (
|
|
79
|
-
<div className="flex items-center justify-center min-h-screen">
|
|
80
|
-
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-gray-900"></div>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
49
|
+
import { createAxiosInterceptor } from '@tgtone/auth-sdk/interceptor';
|
|
50
|
+
import { useTGTAuth } from '@tgtone/auth-sdk/react';
|
|
84
51
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
52
|
+
function Dashboard() {
|
|
53
|
+
const { authClient, session } = useTGTAuth({
|
|
54
|
+
identityUrl: 'https://identity.tgtone.cl',
|
|
55
|
+
appDomain: 'zenith.tgtone.cl',
|
|
56
|
+
});
|
|
89
57
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<nav className="flex justify-between items-center mb-8">
|
|
93
|
-
<h1 className="text-2xl font-bold">Zenith CRM</h1>
|
|
94
|
-
<div className="flex items-center gap-4">
|
|
95
|
-
<span>{user.name}</span>
|
|
96
|
-
<span className="text-sm text-gray-600">{user.tenant_name}</span>
|
|
97
|
-
<button
|
|
98
|
-
onClick={logout}
|
|
99
|
-
className="px-4 py-2 bg-red-500 text-white rounded"
|
|
100
|
-
>
|
|
101
|
-
Cerrar sesión
|
|
102
|
-
</button>
|
|
103
|
-
</div>
|
|
104
|
-
</nav>
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!session) return;
|
|
105
60
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
61
|
+
const api = axios.create({ baseURL: '/api' });
|
|
62
|
+
const cleanup = createAxiosInterceptor(api, authClient, {
|
|
63
|
+
handleRevoked: true,
|
|
64
|
+
excludeUrls: ['/public/'],
|
|
65
|
+
});
|
|
111
66
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
</main>
|
|
115
|
-
</div>
|
|
116
|
-
);
|
|
67
|
+
return cleanup;
|
|
68
|
+
}, [session, authClient]);
|
|
117
69
|
}
|
|
118
|
-
|
|
119
|
-
export default App;
|
|
120
70
|
```
|
|
121
71
|
|
|
122
72
|
---
|
|
123
73
|
|
|
124
|
-
##
|
|
74
|
+
## Vue 3 (Vite)
|
|
125
75
|
|
|
126
|
-
###
|
|
76
|
+
### 1. Instalar
|
|
127
77
|
|
|
128
78
|
```bash
|
|
129
|
-
|
|
130
|
-
cp tgtone-auth-client.ts src/lib/auth/
|
|
79
|
+
npm install @tgtone/auth-sdk
|
|
131
80
|
```
|
|
132
81
|
|
|
133
|
-
###
|
|
82
|
+
### 2. Crear composable
|
|
134
83
|
|
|
135
84
|
```typescript
|
|
136
85
|
// src/composables/useAuth.ts
|
|
137
86
|
import { ref, onMounted } from 'vue';
|
|
138
|
-
import { TGTAuthClient, type
|
|
87
|
+
import { TGTAuthClient, type TGTSession } from '@tgtone/auth-sdk';
|
|
139
88
|
|
|
140
89
|
const authClient = new TGTAuthClient({
|
|
141
|
-
identityUrl: 'https://identity.tgtone.cl',
|
|
142
|
-
appDomain:
|
|
143
|
-
|
|
90
|
+
identityUrl: import.meta.env.VITE_IDENTITY_URL || 'https://identity.tgtone.cl',
|
|
91
|
+
appDomain: window.location.host,
|
|
92
|
+
appKey: import.meta.env.VITE_APP_KEY, // Requerido en dev
|
|
93
|
+
heartbeatIntervalMs: 5 * 60 * 1000,
|
|
94
|
+
debug: import.meta.env.DEV,
|
|
144
95
|
});
|
|
145
96
|
|
|
146
|
-
const
|
|
97
|
+
const session = ref<TGTSession | null>(null);
|
|
147
98
|
const loading = ref(true);
|
|
148
99
|
|
|
149
100
|
export function useAuth() {
|
|
150
101
|
onMounted(async () => {
|
|
151
|
-
|
|
102
|
+
session.value = await authClient.checkSession();
|
|
152
103
|
loading.value = false;
|
|
104
|
+
|
|
105
|
+
if (session.value) {
|
|
106
|
+
authClient.startHeartbeat();
|
|
107
|
+
}
|
|
153
108
|
});
|
|
154
109
|
|
|
155
110
|
const logout = async () => {
|
|
111
|
+
authClient.stopHeartbeat();
|
|
156
112
|
await authClient.logout();
|
|
157
|
-
|
|
113
|
+
session.value = null;
|
|
158
114
|
};
|
|
159
115
|
|
|
160
116
|
const hasRole = (appName: string, roleName: string) => {
|
|
161
117
|
return authClient.hasRole(appName, roleName);
|
|
162
118
|
};
|
|
163
119
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
loading,
|
|
167
|
-
logout,
|
|
168
|
-
hasRole,
|
|
169
|
-
authClient
|
|
120
|
+
const hasAccessToApp = (appName: string) => {
|
|
121
|
+
return authClient.hasAccessToApp(appName);
|
|
170
122
|
};
|
|
123
|
+
|
|
124
|
+
return { session, loading, logout, hasRole, hasAccessToApp, authClient };
|
|
171
125
|
}
|
|
172
126
|
```
|
|
173
127
|
|
|
174
|
-
###
|
|
128
|
+
### 3. Usar en componente
|
|
175
129
|
|
|
176
130
|
```vue
|
|
177
131
|
<!-- src/App.vue -->
|
|
@@ -179,162 +133,87 @@ export function useAuth() {
|
|
|
179
133
|
<div v-if="loading" class="loading">
|
|
180
134
|
<div class="spinner"></div>
|
|
181
135
|
</div>
|
|
182
|
-
|
|
183
|
-
<div v-else-if="
|
|
184
|
-
<nav
|
|
185
|
-
<h1>
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
<span class="tenant">{{ user.tenant_name }}</span>
|
|
189
|
-
<button @click="logout" class="logout-btn">
|
|
190
|
-
Cerrar sesión
|
|
191
|
-
</button>
|
|
192
|
-
</div>
|
|
136
|
+
|
|
137
|
+
<div v-else-if="session" class="app">
|
|
138
|
+
<nav>
|
|
139
|
+
<h1>Mi App</h1>
|
|
140
|
+
<span>{{ session.user.name }} - {{ session.tenantName }}</span>
|
|
141
|
+
<button @click="logout">Cerrar sesión</button>
|
|
193
142
|
</nav>
|
|
194
143
|
|
|
195
|
-
<div v-if="hasRole('baco', 'admin')"
|
|
144
|
+
<div v-if="hasRole('baco', 'admin')">
|
|
196
145
|
Panel de Administración
|
|
197
146
|
</div>
|
|
198
147
|
|
|
199
|
-
<
|
|
200
|
-
<!-- Tu contenido aquí -->
|
|
201
|
-
</main>
|
|
148
|
+
<router-view />
|
|
202
149
|
</div>
|
|
203
150
|
</template>
|
|
204
151
|
|
|
205
152
|
<script setup lang="ts">
|
|
206
153
|
import { useAuth } from './composables/useAuth';
|
|
207
|
-
|
|
208
|
-
const { user, loading, logout, hasRole } = useAuth();
|
|
154
|
+
const { session, loading, logout, hasRole } = useAuth();
|
|
209
155
|
</script>
|
|
210
|
-
|
|
211
|
-
<style scoped>
|
|
212
|
-
.loading {
|
|
213
|
-
display: flex;
|
|
214
|
-
justify-content: center;
|
|
215
|
-
align-items: center;
|
|
216
|
-
height: 100vh;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.spinner {
|
|
220
|
-
border: 4px solid #f3f3f3;
|
|
221
|
-
border-top: 4px solid #3498db;
|
|
222
|
-
border-radius: 50%;
|
|
223
|
-
width: 40px;
|
|
224
|
-
height: 40px;
|
|
225
|
-
animation: spin 1s linear infinite;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
@keyframes spin {
|
|
229
|
-
0% { transform: rotate(0deg); }
|
|
230
|
-
100% { transform: rotate(360deg); }
|
|
231
|
-
}
|
|
232
|
-
</style>
|
|
233
156
|
```
|
|
234
157
|
|
|
235
158
|
---
|
|
236
159
|
|
|
237
|
-
##
|
|
160
|
+
## Next.js 14 (App Router)
|
|
238
161
|
|
|
239
|
-
###
|
|
162
|
+
### 1. Instalar
|
|
240
163
|
|
|
241
164
|
```bash
|
|
242
|
-
|
|
243
|
-
cp tgtone-auth-client.ts lib/auth/
|
|
165
|
+
npm install @tgtone/auth-sdk
|
|
244
166
|
```
|
|
245
167
|
|
|
246
|
-
###
|
|
168
|
+
### 2. Crear Provider (client component)
|
|
247
169
|
|
|
248
170
|
```tsx
|
|
249
171
|
// app/providers/auth-provider.tsx
|
|
250
172
|
'use client';
|
|
251
173
|
|
|
252
|
-
import {
|
|
253
|
-
import {
|
|
254
|
-
|
|
255
|
-
interface AuthContextType {
|
|
256
|
-
user: TGTUser | null;
|
|
257
|
-
loading: boolean;
|
|
258
|
-
logout: () => Promise<void>;
|
|
259
|
-
hasRole: (appName: string, roleName: string) => boolean;
|
|
260
|
-
}
|
|
174
|
+
import { useTGTAuth } from '@tgtone/auth-sdk/react';
|
|
175
|
+
import { createContext, useContext } from 'react';
|
|
176
|
+
import type { UseTGTAuthResult } from '@tgtone/auth-sdk/react';
|
|
261
177
|
|
|
262
|
-
const AuthContext = createContext<
|
|
178
|
+
const AuthContext = createContext<UseTGTAuthResult | null>(null);
|
|
263
179
|
|
|
264
|
-
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
debug: process.env.NODE_ENV === 'development'
|
|
180
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
181
|
+
const auth = useTGTAuth({
|
|
182
|
+
identityUrl: process.env.NEXT_PUBLIC_IDENTITY_URL || 'https://identity.tgtone.cl',
|
|
183
|
+
appDomain: typeof window !== 'undefined' ? window.location.host : '',
|
|
184
|
+
appKey: process.env.NEXT_PUBLIC_APP_KEY,
|
|
185
|
+
enableHeartbeat: true,
|
|
186
|
+
debug: process.env.NODE_ENV === 'development',
|
|
272
187
|
});
|
|
273
188
|
|
|
274
|
-
|
|
275
|
-
async function checkAuth() {
|
|
276
|
-
const authenticatedUser = await authClient.checkSession();
|
|
277
|
-
setUser(authenticatedUser);
|
|
278
|
-
setLoading(false);
|
|
279
|
-
}
|
|
280
|
-
checkAuth();
|
|
281
|
-
}, []);
|
|
282
|
-
|
|
283
|
-
const logout = async () => {
|
|
284
|
-
await authClient.logout();
|
|
285
|
-
setUser(null);
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const hasRole = (appName: string, roleName: string) => {
|
|
289
|
-
return authClient.hasRole(appName, roleName);
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
return (
|
|
293
|
-
<AuthContext.Provider value={{ user, loading, logout, hasRole }}>
|
|
294
|
-
{children}
|
|
295
|
-
</AuthContext.Provider>
|
|
296
|
-
);
|
|
189
|
+
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
|
|
297
190
|
}
|
|
298
191
|
|
|
299
192
|
export const useAuth = () => {
|
|
300
193
|
const context = useContext(AuthContext);
|
|
301
|
-
if (!context)
|
|
302
|
-
throw new Error('useAuth debe usarse dentro de AuthProvider');
|
|
303
|
-
}
|
|
194
|
+
if (!context) throw new Error('useAuth debe usarse dentro de AuthProvider');
|
|
304
195
|
return context;
|
|
305
196
|
};
|
|
306
197
|
```
|
|
307
198
|
|
|
308
|
-
###
|
|
199
|
+
### 3. Envolver en layout
|
|
309
200
|
|
|
310
201
|
```tsx
|
|
311
202
|
// app/layout.tsx
|
|
312
203
|
import { AuthProvider } from './providers/auth-provider';
|
|
313
|
-
import './globals.css';
|
|
314
204
|
|
|
315
|
-
export
|
|
316
|
-
title: 'TGT One Console',
|
|
317
|
-
description: 'Console de administración TGT One',
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
export default function RootLayout({
|
|
321
|
-
children,
|
|
322
|
-
}: {
|
|
323
|
-
children: React.ReactNode;
|
|
324
|
-
}) {
|
|
205
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
325
206
|
return (
|
|
326
207
|
<html lang="es">
|
|
327
208
|
<body>
|
|
328
|
-
<AuthProvider>
|
|
329
|
-
{children}
|
|
330
|
-
</AuthProvider>
|
|
209
|
+
<AuthProvider>{children}</AuthProvider>
|
|
331
210
|
</body>
|
|
332
211
|
</html>
|
|
333
212
|
);
|
|
334
213
|
}
|
|
335
214
|
```
|
|
336
215
|
|
|
337
|
-
###
|
|
216
|
+
### 4. Usar en páginas
|
|
338
217
|
|
|
339
218
|
```tsx
|
|
340
219
|
// app/page.tsx
|
|
@@ -343,45 +222,19 @@ export default function RootLayout({
|
|
|
343
222
|
import { useAuth } from './providers/auth-provider';
|
|
344
223
|
|
|
345
224
|
export default function HomePage() {
|
|
346
|
-
const {
|
|
225
|
+
const { session, loading, logout, hasRole, revokedError } = useAuth();
|
|
347
226
|
|
|
348
|
-
if (loading)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
</div>;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (!user) {
|
|
355
|
-
return null; // Redirigiendo
|
|
356
|
-
}
|
|
227
|
+
if (loading) return <Spinner />;
|
|
228
|
+
if (revokedError) return <RevokedPage error={revokedError} />;
|
|
229
|
+
if (!session) return null;
|
|
357
230
|
|
|
358
231
|
return (
|
|
359
|
-
<div
|
|
360
|
-
<
|
|
361
|
-
<
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
<p className="text-sm text-gray-600">{user.tenant_name}</p>
|
|
366
|
-
</div>
|
|
367
|
-
<button
|
|
368
|
-
onClick={logout}
|
|
369
|
-
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
|
|
370
|
-
>
|
|
371
|
-
Cerrar sesión
|
|
372
|
-
</button>
|
|
373
|
-
</div>
|
|
374
|
-
</nav>
|
|
375
|
-
|
|
376
|
-
{hasRole('console', 'owner') && (
|
|
377
|
-
<div className="bg-blue-100 p-4 rounded mb-6">
|
|
378
|
-
<h2 className="font-bold">Configuración de Owner</h2>
|
|
379
|
-
</div>
|
|
380
|
-
)}
|
|
381
|
-
|
|
382
|
-
<main>
|
|
383
|
-
{/* Tu contenido */}
|
|
384
|
-
</main>
|
|
232
|
+
<div>
|
|
233
|
+
<header>
|
|
234
|
+
<span>{session.user.name}</span>
|
|
235
|
+
<button onClick={logout}>Cerrar sesión</button>
|
|
236
|
+
</header>
|
|
237
|
+
{hasRole('console', 'owner') && <OwnerSettings />}
|
|
385
238
|
</div>
|
|
386
239
|
);
|
|
387
240
|
}
|
|
@@ -389,39 +242,43 @@ export default function HomePage() {
|
|
|
389
242
|
|
|
390
243
|
---
|
|
391
244
|
|
|
392
|
-
##
|
|
245
|
+
## Angular 17+
|
|
393
246
|
|
|
394
|
-
###
|
|
247
|
+
### 1. Instalar
|
|
395
248
|
|
|
396
249
|
```bash
|
|
397
|
-
|
|
398
|
-
cp tgtone-auth-client.ts src/app/services/auth/
|
|
250
|
+
npm install @tgtone/auth-sdk
|
|
399
251
|
```
|
|
400
252
|
|
|
401
|
-
###
|
|
253
|
+
### 2. Crear servicio
|
|
402
254
|
|
|
403
255
|
```typescript
|
|
404
|
-
// src/app/services/auth
|
|
256
|
+
// src/app/services/auth.service.ts
|
|
405
257
|
import { Injectable } from '@angular/core';
|
|
406
|
-
import { BehaviorSubject
|
|
407
|
-
import { TGTAuthClient,
|
|
258
|
+
import { BehaviorSubject } from 'rxjs';
|
|
259
|
+
import { TGTAuthClient, TGTSession, AuthError } from '@tgtone/auth-sdk';
|
|
408
260
|
|
|
409
|
-
@Injectable({
|
|
410
|
-
providedIn: 'root'
|
|
411
|
-
})
|
|
261
|
+
@Injectable({ providedIn: 'root' })
|
|
412
262
|
export class AuthService {
|
|
413
263
|
private authClient: TGTAuthClient;
|
|
414
|
-
private
|
|
264
|
+
private sessionSubject = new BehaviorSubject<TGTSession | null>(null);
|
|
415
265
|
private loadingSubject = new BehaviorSubject<boolean>(true);
|
|
266
|
+
private revokedSubject = new BehaviorSubject<AuthError | null>(null);
|
|
416
267
|
|
|
417
|
-
|
|
418
|
-
|
|
268
|
+
session$ = this.sessionSubject.asObservable();
|
|
269
|
+
loading$ = this.loadingSubject.asObservable();
|
|
270
|
+
revokedError$ = this.revokedSubject.asObservable();
|
|
419
271
|
|
|
420
272
|
constructor() {
|
|
421
273
|
this.authClient = new TGTAuthClient({
|
|
422
274
|
identityUrl: 'https://identity.tgtone.cl',
|
|
423
|
-
appDomain:
|
|
424
|
-
|
|
275
|
+
appDomain: window.location.host,
|
|
276
|
+
appKey: 'console', // Ajustar según la app
|
|
277
|
+
onSessionRevoked: (error) => {
|
|
278
|
+
this.revokedSubject.next(error);
|
|
279
|
+
this.sessionSubject.next(null);
|
|
280
|
+
},
|
|
281
|
+
debug: !environment.production,
|
|
425
282
|
});
|
|
426
283
|
|
|
427
284
|
this.checkSession();
|
|
@@ -429,108 +286,110 @@ export class AuthService {
|
|
|
429
286
|
|
|
430
287
|
private async checkSession() {
|
|
431
288
|
try {
|
|
432
|
-
const
|
|
433
|
-
this.
|
|
289
|
+
const session = await this.authClient.checkSession();
|
|
290
|
+
this.sessionSubject.next(session);
|
|
291
|
+
|
|
292
|
+
if (session) {
|
|
293
|
+
this.authClient.startHeartbeat();
|
|
294
|
+
}
|
|
434
295
|
} catch (error) {
|
|
435
296
|
console.error('Error checking session:', error);
|
|
436
|
-
this.userSubject.next(null);
|
|
437
297
|
} finally {
|
|
438
298
|
this.loadingSubject.next(false);
|
|
439
299
|
}
|
|
440
300
|
}
|
|
441
301
|
|
|
442
302
|
async logout() {
|
|
303
|
+
this.authClient.stopHeartbeat();
|
|
443
304
|
await this.authClient.logout();
|
|
444
|
-
this.
|
|
305
|
+
this.sessionSubject.next(null);
|
|
445
306
|
}
|
|
446
307
|
|
|
447
308
|
hasRole(appName: string, roleName: string): boolean {
|
|
448
309
|
return this.authClient.hasRole(appName, roleName);
|
|
449
310
|
}
|
|
450
311
|
|
|
451
|
-
|
|
452
|
-
return this.
|
|
312
|
+
hasAccessToApp(appName: string): boolean {
|
|
313
|
+
return this.authClient.hasAccessToApp(appName);
|
|
453
314
|
}
|
|
454
315
|
}
|
|
455
316
|
```
|
|
456
317
|
|
|
457
|
-
###
|
|
318
|
+
### 3. Usar en componente
|
|
458
319
|
|
|
459
320
|
```typescript
|
|
460
321
|
// src/app/app.component.ts
|
|
461
322
|
import { Component } from '@angular/core';
|
|
462
|
-
import { AuthService } from './services/auth
|
|
323
|
+
import { AuthService } from './services/auth.service';
|
|
463
324
|
|
|
464
325
|
@Component({
|
|
465
326
|
selector: 'app-root',
|
|
466
327
|
template: `
|
|
467
|
-
<div *ngIf="
|
|
328
|
+
<div *ngIf="auth.loading$ | async" class="loading">
|
|
468
329
|
<div class="spinner"></div>
|
|
469
330
|
</div>
|
|
470
331
|
|
|
471
|
-
<div *ngIf="
|
|
472
|
-
<
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
<span>{{ user.name }}</span>
|
|
476
|
-
<span class="tenant">{{ user.tenant_name }}</span>
|
|
477
|
-
<button (click)="logout()" class="logout-btn">
|
|
478
|
-
Cerrar sesión
|
|
479
|
-
</button>
|
|
480
|
-
</div>
|
|
481
|
-
</nav>
|
|
482
|
-
|
|
483
|
-
<div *ngIf="authService.hasRole('zenith', 'admin')" class="admin-panel">
|
|
484
|
-
Panel de Administración
|
|
485
|
-
</div>
|
|
332
|
+
<div *ngIf="auth.revokedError$ | async as error" class="blocked">
|
|
333
|
+
<h2>Acceso bloqueado</h2>
|
|
334
|
+
<p>{{ error.message }}</p>
|
|
335
|
+
</div>
|
|
486
336
|
|
|
487
|
-
|
|
337
|
+
<div *ngIf="(auth.session$ | async) as session">
|
|
338
|
+
<nav>
|
|
339
|
+
<h1>Mi App</h1>
|
|
340
|
+
<span>{{ session.user.name }}</span>
|
|
341
|
+
<button (click)="logout()">Cerrar sesión</button>
|
|
342
|
+
</nav>
|
|
343
|
+
<div *ngIf="auth.hasRole('zenith', 'admin')">Admin Panel</div>
|
|
344
|
+
<router-outlet />
|
|
488
345
|
</div>
|
|
489
346
|
`,
|
|
490
|
-
styleUrls: ['./app.component.css']
|
|
491
347
|
})
|
|
492
348
|
export class AppComponent {
|
|
493
|
-
constructor(public
|
|
349
|
+
constructor(public auth: AuthService) {}
|
|
494
350
|
|
|
495
351
|
logout() {
|
|
496
|
-
this.
|
|
352
|
+
this.auth.logout();
|
|
497
353
|
}
|
|
498
354
|
}
|
|
499
355
|
```
|
|
500
356
|
|
|
501
357
|
---
|
|
502
358
|
|
|
503
|
-
##
|
|
359
|
+
## Variables de Entorno por App
|
|
504
360
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
361
|
+
| App | `VITE_APP_KEY` | `VITE_APP_DOMAIN` |
|
|
362
|
+
|-----|---------------|-------------------|
|
|
363
|
+
| Zenith (CRM) | `zenith` | `zenith.tgtone.cl` |
|
|
364
|
+
| Baco (Wine) | `baco` | `baco.tgtone.cl` |
|
|
365
|
+
| Console (Admin) | `console` | `console.tgtone.cl` |
|
|
510
366
|
|
|
511
|
-
### **Baco (Wine Management)**
|
|
512
367
|
```env
|
|
513
|
-
|
|
368
|
+
# .env.local (ejemplo para Baco en dev)
|
|
514
369
|
VITE_IDENTITY_URL=https://identity.tgtone.cl
|
|
370
|
+
VITE_APP_KEY=baco
|
|
515
371
|
```
|
|
516
372
|
|
|
517
|
-
|
|
518
|
-
```env
|
|
519
|
-
VITE_APP_DOMAIN=console.tgtone.cl
|
|
520
|
-
VITE_IDENTITY_URL=https://identity.tgtone.cl
|
|
521
|
-
```
|
|
373
|
+
> En producción, si el dominio es `baco.tgtone.cl`, el SDK extrae el app key automáticamente y `appKey` no es necesario.
|
|
522
374
|
|
|
523
375
|
---
|
|
524
376
|
|
|
525
|
-
##
|
|
526
|
-
|
|
527
|
-
- [ ]
|
|
528
|
-
- [ ] Configurar variables de entorno
|
|
529
|
-
- [ ]
|
|
530
|
-
- [ ]
|
|
531
|
-
- [ ]
|
|
532
|
-
- [ ]
|
|
533
|
-
- [ ] Probar
|
|
534
|
-
- [ ]
|
|
535
|
-
- [ ]
|
|
536
|
-
|
|
377
|
+
## Checklist de Integración
|
|
378
|
+
|
|
379
|
+
- [ ] `npm install @tgtone/auth-sdk`
|
|
380
|
+
- [ ] Configurar variables de entorno (`VITE_IDENTITY_URL`, `VITE_APP_KEY`)
|
|
381
|
+
- [ ] Implementar hook/composable/provider según framework
|
|
382
|
+
- [ ] Manejar estados: `loading`, `session`, `revokedError`
|
|
383
|
+
- [ ] Bloquear renderizado mientras `loading === true` (evita que Router limpie `?token=`)
|
|
384
|
+
- [ ] Configurar interceptor Axios/Fetch para API calls
|
|
385
|
+
- [ ] Probar flujo login/logout
|
|
386
|
+
- [ ] Probar detección de sesión revocada (desactivar usuario en Console)
|
|
387
|
+
- [ ] Verificar roles y permisos por app
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Referencia
|
|
392
|
+
|
|
393
|
+
- **[QUICKSTART_REACT.md](./QUICKSTART_REACT.md)** - Quick Start detallado para React
|
|
394
|
+
- **[API.md](./API.md)** - Referencia completa de API
|
|
395
|
+
- **[CHANGELOG.md](../CHANGELOG.md)** - Historial de cambios
|