Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"lodash-es": "^4.17.21",
"luxon": "^3.3.0",
"mathjs": "^13.0.0",
"ngx-cookie-service": "^16.1.0",
"ngx-json-viewer": "^3",
"ngx-linky": "^4.0.0",
"ngx-material-luxon": "^1.1.1",
Expand Down
6 changes: 6 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import { LayoutModule } from "_layout/layout.module";
import { AppConfigService } from "app-config.service";
import { AppThemeService } from "app-theme.service";
import { SnackbarInterceptor } from "shared/interceptors/snackbar.interceptor";
import { AuthService } from "shared/services/auth/auth.service";
import { InternalStorage, SDKStorage } from "shared/services/auth/base.storage";
import { CookieService } from "ngx-cookie-service";

const appConfigInitializerFn = (appConfig: AppConfigService) => {
return () => appConfig.loadAppConfig();
Expand Down Expand Up @@ -93,11 +96,14 @@ const appThemeInitializerFn = (appTheme: AppThemeService) => {
subscriptSizing: "dynamic",
},
},
AuthService,
AppThemeService,
UserApi,
SampleApi,
Title,
MatNativeDateModule,
{ provide: InternalStorage, useClass: CookieService },
{ provide: SDKStorage, useClass: CookieService },
],
bootstrap: [AppComponent],
})
Expand Down
129 changes: 129 additions & 0 deletions src/app/shared/services/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Injectable, Inject } from "@angular/core";
import { InternalStorage } from "./base.storage";
import { User } from "shared/sdk";

export interface AccessTokenInterface {
id?: string;
ttl?: number;
scopes?: [string];
created?: Date;
userId?: string;
user?: User;
}

export class SDKToken implements AccessTokenInterface {
id: string = null;
ttl: number = null;
scopes: [string] = null;
created: Date = null;
userId: string = null;
user: User = null;
rememberMe: boolean = null;
constructor(data?: AccessTokenInterface) {
Object.assign(this, data);
}
}

@Injectable()
export class AuthService {
private token = new SDKToken();

protected prefix = "";

/**
* NOTE: This was the suggestion from the ai-bot review to simplify the storage and logic here
* (might be worth checking in the future if we want to make some changes in the cookies storage structure and keep them in one encoded object):
* https://github.com/SciCatProject/frontend/pull/1632#discussion_r1824033871
*/
constructor(@Inject(InternalStorage) protected storage: InternalStorage) {
// TODO: Test if all this works with the new changes and removal of any types
this.token.id = this.load("id");
this.token.user = JSON.parse(this.load("user") || null);
this.token.userId = this.load("userId");
this.token.created = new Date(this.load("created"));
this.token.ttl = parseInt(this.load("ttl"));
this.token.rememberMe = this.load("rememberMe") === "true";
}

protected load(prop: string) {
return decodeURIComponent(this.storage.get(`${this.prefix}${prop}`));
}

protected persist(
prop: string,
value: string | User | number | Date | boolean,
expires?: Date,
): void {
try {
this.storage.set(
`${this.prefix}${prop}`,
typeof value === "object" ? JSON.stringify(value) : value,
this.token.rememberMe ? expires : null,
);
} catch (err) {
throw new Error(
`Cannot access local/session storage: ${JSON.stringify(err)}`,
);
}
}

public clear(): void {
Object.keys(this.token).forEach((prop: string) =>
this.storage.delete(`${this.prefix}${prop}`),
);
this.token = new SDKToken();
}

public setRememberMe(value: boolean): void {
this.token.rememberMe = value;
}

public setUser(user: User) {
this.token.user = user;
this.save();
}

public setToken(token: SDKToken): void {
this.token = Object.assign({}, this.token, token);
this.save();
}

public getToken(): SDKToken {
return <SDKToken>this.token;
}

public getAccessTokenId(): string {
return this.token.id;
}

public getCurrentUserId() {
return this.token.userId;
}

public getCurrentUserData() {
return typeof this.token.user === "string"
? JSON.parse(this.token.user)
: this.token.user;
}

public isAuthenticated() {
return !(
this.getCurrentUserId() === "" ||
this.getCurrentUserId() == null ||
this.getCurrentUserId() == "null"
);
}

public save(): boolean {
const today = new Date();
const expires = new Date(today.getTime() + this.token.ttl * 1000);
this.persist("id", this.token.id, expires);
this.persist("user", this.token.user, expires);
this.persist("userId", this.token.userId, expires);
this.persist("created", this.token.created, expires);
this.persist("ttl", this.token.ttl, expires);
this.persist("rememberMe", this.token.rememberMe, expires);

return true;
}
}
15 changes: 15 additions & 0 deletions src/app/shared/services/auth/base.storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-disable @typescript-eslint/no-unused-vars */

export class BaseStorage {
get(key: string): string | undefined {
return undefined;
}

set(key: string, value: string | number | boolean, expires?: Date): void {}

delete(key: string): void {}
}

export class InternalStorage extends BaseStorage {}

export class SDKStorage extends BaseStorage {}
32 changes: 32 additions & 0 deletions src/app/shared/services/auth/storage.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Injectable } from "@angular/core";

@Injectable()
export class LocalStorageBrowser {
private parse(value: string) {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}

get(key: string) {
const data: string = localStorage.getItem(key);
return this.parse(data);
}

set(key: string, value: string): void {
localStorage.setItem(
key,
typeof value === "object" ? JSON.stringify(value) : value,
);
}

delete(key: string): void {
if (localStorage[key]) {
localStorage.removeItem(key);
} else {
throw new Error(`Cannot remove non-existent key: ${key}`);
}
}
}