Skip to content

Commit d59da20

Browse files
Copied over equivalent functionality from minsky_fe
1 parent 1315904 commit d59da20

9 files changed

Lines changed: 15594 additions & 11234 deletions

File tree

gui-js/apps/minsky-web/src/app/app-routing.module.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { NgModule } from '@angular/core';
2-
import { RouterModule, Routes } from '@angular/router';
1+
import { inject, NgModule } from '@angular/core';
2+
import { CanActivateFn, Router, RouterModule, Routes } from '@angular/router';
33
import {
44
ConnectDatabaseComponent,
55
CliInputComponent,
@@ -30,6 +30,20 @@ import {
3030
PickSlicesComponent,
3131
LockHandlesComponent
3232
} from '@minsky/ui-components';
33+
import { AuthService } from './app.authservices';
34+
35+
const checkAuth: CanActivateFn = (route, state) => {
36+
console.log("called")
37+
const authService = inject(AuthService); // ✅ Inject AuthService correctly
38+
const router = inject(Router); // ✅ Inject Router correctly
39+
40+
if (!authService.isUserSignedIn) {
41+
router.navigate(['']); // Redirect to home page (or '/signin')
42+
return false;
43+
}
44+
return true;
45+
};
46+
3347

3448
const routes: Routes = [
3549
{
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// import { Injectable } from '@angular/core';
2+
// import { AuthConfig } from 'angular-oauth2-oidc';
3+
// import { OAuthService } from 'angular-oauth2-oidc';
4+
5+
6+
// export const authConfig: AuthConfig = {
7+
// clientId: '991368205626-sanh7rjvjlmr89jfee9dn8i3lvsa9fjj.apps.googleusercontent.com', // Replace with your clientId
8+
// redirectUri: window.location.origin,
9+
// responseType: 'token',
10+
// scope: 'openid profile email', // Adjust as needed
11+
// issuer: 'https://accounts.google.com',
12+
// strictDiscoveryDocumentValidation: false,
13+
// showDebugInformation: true, // Optional for debugging
14+
// };
15+
16+
// @Injectable({
17+
// providedIn: 'root'
18+
// })
19+
// export class AuthService {
20+
// constructor(private oauthService: OAuthService) {
21+
// this.configureOAuth();
22+
// }
23+
24+
// private configureOAuth() {
25+
// this.oauthService.configure(authConfig);
26+
// this.oauthService.loadDiscoveryDocumentAndTryLogin();
27+
// }
28+
29+
// public login() {
30+
// this.oauthService.initImplicitFlow();
31+
// }
32+
33+
// public logout() {
34+
// this.oauthService.logOut();
35+
// }
36+
37+
// public get identityClaims() {
38+
// return this.oauthService.getIdentityClaims();
39+
// }
40+
// }
41+
42+
43+
import { Injectable,OnDestroy } from '@angular/core';
44+
import { Clerk } from '@clerk/clerk-js';
45+
import { BehaviorSubject, firstValueFrom, Observable, tap } from 'rxjs';
46+
import { AppConfig } from '../environments/environment';
47+
import { HttpClient } from '@angular/common/http';
48+
@Injectable({
49+
providedIn: 'root', // Ensures it's available globally
50+
})
51+
export class AuthService implements OnDestroy {
52+
private clerk: Clerk | undefined;
53+
private isUserSignedInSubject = new BehaviorSubject<boolean>(false);
54+
isUserSignedIn = this.isUserSignedInSubject.asObservable(); // Observable for status
55+
56+
private Ravel_subscriptionStatus = new BehaviorSubject<boolean>(false);
57+
Ravel_subscriptionStatus$ = this.Ravel_subscriptionStatus.asObservable();
58+
59+
private Minsky_subscriptionStatus = new BehaviorSubject<boolean>(false);
60+
Minsky_subscriptionStatus$ = this.Minsky_subscriptionStatus.asObservable();
61+
62+
private Artifact_subscriptionStatus = new BehaviorSubject<boolean>(false);
63+
Artifact_subscriptionStatus$ = this.Artifact_subscriptionStatus.asObservable();
64+
65+
private subscriptions = [
66+
{ key: 'Minsky_subscription', subject: this.Minsky_subscriptionStatus },
67+
{ key: 'Ravel_subscription', subject: this.Ravel_subscriptionStatus },
68+
{ key: 'Artifact_subscription', subject: this.Artifact_subscriptionStatus },
69+
];
70+
71+
private sessionToken: string | null = null;
72+
private baseUrl = AppConfig.apiUrl;
73+
private token = new BehaviorSubject<string | null>(null);
74+
constructor( private http:HttpClient){}
75+
76+
private clerkInitialized = new BehaviorSubject<boolean>(false);
77+
clerkInitialized$ = this.clerkInitialized.asObservable();
78+
79+
async initializeClerk(clerkPubKey: string) {
80+
if (!clerkPubKey) {
81+
console.error('Clerk Publishable Key is missing');
82+
return;
83+
}
84+
85+
this.clerk = new Clerk(clerkPubKey);
86+
await this.clerk.load();
87+
88+
this.sessionToken = await this.clerk.session?.getToken();
89+
if (this.sessionToken) {
90+
await this.verifyBackendAuth(this.sessionToken); // Verify with FastAPI
91+
} else {
92+
this.isUserSignedInSubject.next(false); // Mark user as signed out
93+
}
94+
95+
this.clerkInitialized.next(true);
96+
97+
}
98+
99+
async verifyBackendAuth(sessionToken: string) {
100+
try {
101+
const response = await firstValueFrom(
102+
this.http.get(`${this.baseUrl}/protected`, {
103+
headers: { Authorization: `Bearer ${sessionToken}` },
104+
})
105+
);
106+
this.isUserSignedInSubject.next(true); // User is authenticated
107+
const user = this.clerk.user;
108+
if (user) {
109+
this.subscriptions.forEach(({ key, subject }) => {
110+
subject.next(user.publicMetadata[key] === 'active');
111+
});
112+
}
113+
} catch (error) {
114+
console.error('Backend authentication failed:', error);
115+
this.isUserSignedInSubject.next(false);
116+
this.sessionToken=null
117+
await this.handleSessionInvalid(); // User is NOT authenticated
118+
}
119+
}
120+
121+
122+
async handleSessionInvalid() {
123+
try {
124+
console.log('Session invalid. Attempting to refresh session...');
125+
this.sessionToken = null;
126+
if (this.sessionToken) {
127+
console.log('Session refreshed. Re-authenticating...');
128+
await this.verifyBackendAuth(this.sessionToken);
129+
} else {
130+
console.log('Session refresh failed. Logging out user...');
131+
await this.signOut();
132+
this.isUserSignedInSubject.next(false);
133+
}
134+
} catch (refreshError) {
135+
console.error('Session refresh failed:', refreshError);
136+
await this.signOut();
137+
this.isUserSignedInSubject.next(false);
138+
}
139+
}
140+
141+
142+
async openSignIn() {
143+
if (!this.clerk) {
144+
console.error('Clerk is not initialized');
145+
return;
146+
}
147+
await this.clerk.openSignIn();
148+
// If a user object exists after closing the pop-up, get the new token and verify it.
149+
if (this.clerk.user) {
150+
this.sessionToken = await this.clerk.session?.getToken();
151+
if (this.sessionToken) {
152+
await this.verifyBackendAuth(this.sessionToken); // Run the backend check
153+
}
154+
} else {
155+
this.isUserSignedInSubject.next(false); // Update sign-in status
156+
}
157+
//this.isUserSignedInSubject.next(!!this.clerk.user);
158+
}
159+
160+
async openSignup() {
161+
if (!this.clerk) {
162+
console.error('Clerk is not initialized');
163+
return;
164+
}
165+
await this.clerk.openSignUp();
166+
}
167+
168+
async signOut() {
169+
if (!this.clerk) {
170+
console.error('Clerk is not initialized');
171+
return;
172+
}
173+
await this.clerk.signOut();
174+
this.isUserSignedInSubject.next(false);// Update sign-in status
175+
this.sessionToken=null
176+
}
177+
async ngOnDestroy() {
178+
if (this.clerk && this.clerk.user) {
179+
await this.clerk.signOut();
180+
console.log('User signed out on application stop');
181+
}
182+
}
183+
184+
getUserDetails(): { userId: string | null; email: string | null;username: string | null } {
185+
if (!this.clerk) {
186+
console.error('Clerk is not initialized');
187+
return { userId: null, email: null,username: null };
188+
}
189+
190+
const user = this.clerk.user;
191+
if (user) {
192+
const userId = user.id; // Clerk user object contains the `id` property
193+
const email = user.emailAddresses?.[0]?.emailAddress || null;
194+
const username = user.username || `${user.firstName || ''} ${user.lastName || ''}`.trim() || null;
195+
return { userId, email,username };
196+
}
197+
198+
console.warn('No user is signed in');
199+
return { userId: null, email: null,username: null };
200+
}
201+
202+
getUserEmail(): string | null {
203+
const userDetails = this.getUserDetails();
204+
return userDetails.email;
205+
}
206+
207+
checkDomainExists(domain: string): Observable<{ exists: boolean }> {
208+
return this.http.get<{ exists: boolean }>(`${this.baseUrl}/domain/exists/${domain}`);
209+
}
210+
211+
Adminlogin(username: string, password: string): Observable<any> {
212+
return this.http.post<any>(`${this.baseUrl}/token`, { username, password }).pipe(
213+
tap(response => {
214+
if (response.access_token) { // ✅ Check for token before storing
215+
this.token.next(response.access_token);
216+
localStorage.setItem('token', response.access_token);
217+
}
218+
})
219+
);
220+
}
221+
getToken() {
222+
return this.token.value || localStorage.getItem('token');
223+
}
224+
225+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export const AppConfig = {
22
production: false,
33
environment: 'LOCAL',
4+
// backend ravelation server
5+
apiUrl: 'https://minskybe-x7dj1.sevalla.app',
46
};

gui-js/libs/ui-components/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export * from './lib/header/header.component';
1818
export * from './lib/import-csv/import-csv.component';
1919
export * from './lib/input-modal/input-modal.component';
2020
export * from './lib/lock-handles/lock-handles.component';
21+
export * from './lib/login/login.component';
2122
export * from './lib/new-database/new-database.component';
2223
export * from './lib/new-pub-tab/new-pub-tab.component';
2324
export * from './lib/page-not-found/page-not-found.component';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
<head>
3+
<script src="https://accounts.google.com/gsi/client" async defer></script>
4+
</head>
5+
<div class="container-fluid vh-100 d-flex justify-content-center align-items-center">
6+
<div class="row w-100">
7+
<!-- Image Column -->
8+
<!-- <div class="col-md-6" >
9+
<img src="assets/images/icons/recalculate.svg" alt="Login Image" class="full-image">
10+
11+
</div> -->
12+
13+
<div class="col-md-6 d-flex justify-content-center align-items-center">
14+
<mat-card class="login-card p-4 shadow-sm" style="float: left;">
15+
<div class="col-md-6" >
16+
<img src="assets/images/loginlogo2.png" alt="Login Image" class="full-image">
17+
</div>
18+
</mat-card>
19+
</div>
20+
21+
22+
23+
<!-- Login Card Column -->
24+
<div class="col-md-6 d-flex justify-content-center align-items-center">
25+
<mat-card class="login-card p-4 shadow-sm">
26+
<mat-card-title class="text-center">Login</mat-card-title>
27+
<form (submit)="login()">
28+
<input type="text" class="form-control" placeholder="Username" [(ngModel)]="username" name="username">
29+
<input type="password" class="form-control" placeholder="Password" [(ngModel)]="password" name="password">
30+
<button type="submit" class="custom-link-button">Login</button>
31+
</form>
32+
33+
</mat-card>
34+
</div>
35+
<p *ngIf="errorMessage" style="color: red;">{{ errorMessage }}</p>
36+
</div>
37+
</div>

0 commit comments

Comments
 (0)