@verisoft/security-core 18.0.0 → 18.3.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 CHANGED
@@ -1,9 +1,11 @@
1
- {
2
- "name": "@verisoft/security-core",
3
- "version": "18.0.0",
4
- "peerDependencies": {
5
- "@angular/common": "^18.2.0",
6
- "@angular/core": "^18.2.0"
7
- },
8
- "sideEffects": false
9
- }
1
+ {
2
+ "name": "@verisoft/security-core",
3
+ "version": "18.3.0",
4
+ "peerDependencies": {
5
+ "@angular/core": "^18.2.0",
6
+ "rxjs": "~7.8.0",
7
+ "@angular/router": "18.2.8",
8
+ "@ngrx/store": "18.0.2"
9
+ },
10
+ "sideEffects": false
11
+ }
@@ -3,16 +3,16 @@ import {
3
3
  Input,
4
4
  TemplateRef,
5
5
  ViewContainerRef,
6
- OnInit,
7
6
  OnDestroy,
8
7
  } from '@angular/core';
9
8
  import { distinctUntilChanged, Subscription } from 'rxjs';
10
9
  import { AuthContextService } from '../services';
11
10
 
12
11
  @Directive({
12
+ standalone: true,
13
13
  selector: '[hasPermission]',
14
14
  })
15
- export class HasPermissionDirective<T> implements OnInit, OnDestroy {
15
+ export class HasPermissionDirective<T> implements OnDestroy {
16
16
  private requiredPermissions: string | string[] = '';
17
17
  private sub?: Subscription;
18
18
 
@@ -28,10 +28,6 @@ export class HasPermissionDirective<T> implements OnInit, OnDestroy {
28
28
  this.updateView();
29
29
  }
30
30
 
31
- ngOnInit(): void {
32
- this.updateView();
33
- }
34
-
35
31
  ngOnDestroy(): void {
36
32
  this.unregister();
37
33
  }
@@ -42,7 +38,7 @@ export class HasPermissionDirective<T> implements OnInit, OnDestroy {
42
38
 
43
39
  private updateView(): void {
44
40
  this.unregister();
45
- if (!this.requiredPermissions) {
41
+ if (this.requiredPermissions || this.requiredPermissions.length) {
46
42
  this.sub = this.authContext
47
43
  .hasRequiredPermission(this.requiredPermissions)
48
44
  .pipe(distinctUntilChanged())
@@ -3,16 +3,16 @@ import {
3
3
  Input,
4
4
  TemplateRef,
5
5
  ViewContainerRef,
6
- OnInit,
7
6
  OnDestroy,
8
7
  } from '@angular/core';
9
8
  import { distinctUntilChanged, Subscription } from 'rxjs';
10
9
  import { AuthContextService } from '../services';
11
10
 
12
11
  @Directive({
12
+ standalone: true,
13
13
  selector: '[hasRole]',
14
14
  })
15
- export class HasRoleDirective<T> implements OnInit, OnDestroy {
15
+ export class HasRoleDirective<T> implements OnDestroy {
16
16
  private requiredRoles: string | string[] | undefined;
17
17
  private sub?: Subscription;
18
18
 
@@ -28,10 +28,6 @@ export class HasRoleDirective<T> implements OnInit, OnDestroy {
28
28
  this.updateView();
29
29
  }
30
30
 
31
- ngOnInit(): void {
32
- this.updateView();
33
- }
34
-
35
31
  ngOnDestroy(): void {
36
32
  this.unregister();
37
33
  }
@@ -42,7 +38,7 @@ export class HasRoleDirective<T> implements OnInit, OnDestroy {
42
38
 
43
39
  private updateView(): void {
44
40
  this.unregister();
45
- if (this.requiredRoles) {
41
+ if (this.requiredRoles || this.requiredRoles?.length) {
46
42
  this.sub = this.authContext
47
43
  .hasRequiredRole(this.requiredRoles)
48
44
  .pipe(distinctUntilChanged())
@@ -19,7 +19,7 @@ export class AuthGuard implements CanActivate, CanActivateChild {
19
19
  private config = inject<SecurityConfig>(SECURITY_CONFIG);
20
20
  private router = inject(Router);
21
21
  private authContext = inject(AuthContextService);
22
-
22
+
23
23
  canActivate(route: ActivatedRouteSnapshot): Observable<boolean | UrlTree> {
24
24
  return this.checkPermissionsAndRolesAndNavigate(route, this.config.notAuthorizedPage);
25
25
  }
@@ -34,16 +34,12 @@ export class AuthGuard implements CanActivate, CanActivateChild {
34
34
  route: ActivatedRouteSnapshot,
35
35
  notAuthorizedUrl: string | undefined
36
36
  ): Observable<boolean | UrlTree> {
37
- const requiredPermissions = route.data['permissions'] as
38
- | string
39
- | string[]
40
- | undefined;
37
+ const requiredPermissions = route.data['permissions'] as | string | string[] | undefined;
41
38
  const requiredRoles = route.data['roles'] as string | string[] | undefined;
42
39
 
43
40
  return this.authContext.user$.pipe(
44
41
  map(
45
- (user) =>
46
- user &&
42
+ (user) => user &&
47
43
  (!requiredPermissions ||
48
44
  hasRequiredPermission(user, requiredPermissions)) &&
49
45
  (!requiredRoles || hasRequiredPermission(user, requiredRoles))
@@ -1,7 +1,8 @@
1
1
  export interface AuthenticatedUser {
2
+ userId?: string;
2
3
  userName: string;
3
4
  email: string;
4
- displayName: string;
5
+ displayName?: string;
5
6
  roles: string[] | undefined;
6
7
  permissions: string[] | undefined;
7
- }
8
+ }
@@ -1,7 +1,9 @@
1
1
 
2
2
  export interface SecurityConfig {
3
3
  tokenStorageKey: string;
4
+ contextTokenStorageKey?: string;
4
5
  loginPage?: string;
6
+ logoutPage?: string;
5
7
  notAuthorizedPage?: string;
6
8
  sendTokenHeader?: boolean;
7
- }
9
+ }
@@ -18,11 +18,11 @@ function hasItems(
18
18
  userItems: string[] | undefined,
19
19
  neededItems: string | string[]
20
20
  ): boolean {
21
- if (!neededItems) {
21
+ if (!neededItems || !neededItems.length) {
22
22
  return true;
23
23
  }
24
24
 
25
- if (!userItems) {
25
+ if (!userItems || !userItems.length) {
26
26
  return false;
27
27
  }
28
28
 
@@ -51,18 +51,16 @@ export function convertJWTToUser(
51
51
  if (parts.length < 2) {
52
52
  return undefined;
53
53
  }
54
+ const payload = decodeJwtPayload(parts[1]);
54
55
 
55
- const payloadBase64 = parts[1];
56
- const payloadJson = atob(payloadBase64);
57
- const payload = JSON.parse(payloadJson);
58
-
59
- const userName = payload['sub'] ?? payload['nameid'];
56
+ const userName = payload['unique_name'] ?? payload['nameid'];
60
57
  if (!userName) {
61
58
  return undefined;
62
59
  }
63
60
 
64
61
  const user: AuthenticatedUser = {
65
62
  userName,
63
+ userId: payload['nameid'],
66
64
  email: payload['email'],
67
65
  displayName: payload['name'],
68
66
  roles: convertToArray(payload, 'role'),
@@ -75,6 +73,19 @@ export function convertJWTToUser(
75
73
  }
76
74
  }
77
75
 
76
+ export function decodeJwtPayload(jwt: string): any {
77
+ const base64 = jwt.replace(/-/g, '+').replace(/_/g, '/');
78
+
79
+ const jsonPayload = decodeURIComponent(
80
+ atob(base64)
81
+ .split('')
82
+ .map(c => '%' + c.charCodeAt(0).toString(16).padStart(2, '0'))
83
+ .join('')
84
+ );
85
+
86
+ return JSON.parse(jsonPayload);
87
+ }
88
+
78
89
  function convertToArray(
79
90
  item: { [key: string]: string | string[] },
80
91
  propertyName: string
@@ -7,7 +7,6 @@ import {
7
7
  } from '@angular/core';
8
8
  import { Router } from '@angular/router';
9
9
  import { StoreModule } from '@ngrx/store';
10
- import { HasPermissionDirective, HasRoleDirective } from './directives';
11
10
  import { AuthGuard } from './guards';
12
11
  import { SecurityConfig } from './models';
13
12
  import {
@@ -28,15 +27,13 @@ export function provideSecurity(
28
27
  return [
29
28
  AuthGuard,
30
29
  SECURITY_INITIALIZER_PROVIDER,
31
- { provide: TOKEN_PROVIDER, useClass: LocalStorageTokenProvider },
30
+ { provide: SECURITY_CONTEXT_TOKEN_PROVIDER, useClass: LocalStorageTokenProvider },
32
31
  { provide: SECURITY_CONFIG, useValue: securityConfig },
33
32
  ];
34
33
  }
35
34
 
36
35
  @NgModule({
37
- declarations: [HasPermissionDirective, HasRoleDirective],
38
36
  imports: [StoreModule.forFeature(SecurityFeature)],
39
- exports: [HasPermissionDirective, HasRoleDirective],
40
37
  })
41
38
  export class SecurityModule {
42
39
  static forRoot(
@@ -49,13 +46,13 @@ export class SecurityModule {
49
46
  }
50
47
  }
51
48
 
52
- export const TOKEN_PROVIDER = new InjectionToken('SECURITY_TOKEN_PROVIDER');
49
+ export const SECURITY_CONTEXT_TOKEN_PROVIDER = new InjectionToken('SECURITY_CONTEXT_TOKEN_PROVIDER');
53
50
 
54
51
  export const SECURITY_CONFIG = new InjectionToken('SECURITY_CONFIG');
55
52
 
56
53
  export const SECURITY_INITIALIZER_PROVIDER: Provider = {
57
54
  provide: APP_INITIALIZER,
58
55
  useFactory: securityInitializerFactory,
59
- deps: [TOKEN_PROVIDER, AuthContextService, SECURITY_CONFIG, Router],
56
+ deps: [SECURITY_CONTEXT_TOKEN_PROVIDER, AuthContextService, SECURITY_CONFIG, Router],
60
57
  multi: true,
61
58
  };
@@ -1,5 +1,7 @@
1
1
  export * from './auth-context.service';
2
- export * from './local-token-provider';
2
+ export * from './local-storage-token-provider';
3
3
  export * from './security-initializer';
4
4
  export * from './session-token-provider';
5
5
  export * from './token-provider';
6
+ export * from './login.service';
7
+ export * from './logout.service';
@@ -0,0 +1,23 @@
1
+ import { inject, Injectable } from '@angular/core';
2
+ import { Observable, of } from 'rxjs';
3
+ import { SecurityConfig } from '../models';
4
+ import { SECURITY_CONFIG } from '../provider';
5
+ import { TokenProvider } from './token-provider';
6
+
7
+ @Injectable()
8
+ export class LocalStorageTokenProvider implements TokenProvider {
9
+ private config = inject<SecurityConfig>(SECURITY_CONFIG);
10
+
11
+ getToken(): Observable<string | undefined> {
12
+ const token = localStorage.getItem(this.config.tokenStorageKey);
13
+ return of(token ?? undefined);
14
+ }
15
+
16
+ setToken(token: string): void {
17
+ localStorage.setItem(this.config.tokenStorageKey, token);
18
+ }
19
+
20
+ removeToken(): void {
21
+ localStorage.clear();
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ import { inject, Injectable } from '@angular/core';
2
+ import { Router } from '@angular/router';
3
+ import { convertJWTToUser, SecurityConfig } from '../models';
4
+ import { SECURITY_CONFIG, SECURITY_CONTEXT_TOKEN_PROVIDER } from '../provider';
5
+ import { AuthContextService } from './auth-context.service';
6
+ import { LocalStorageTokenProvider } from './local-storage-token-provider';
7
+
8
+ @Injectable()
9
+ export class LoginService {
10
+ private config = inject<SecurityConfig>(SECURITY_CONFIG);
11
+ private tokenProvider = inject<LocalStorageTokenProvider>(SECURITY_CONTEXT_TOKEN_PROVIDER);
12
+ private authService = inject(AuthContextService);
13
+ private router = inject(Router);
14
+
15
+ login(token?: string): void {
16
+ if (token) {
17
+ this.tokenProvider.setToken(token);
18
+ this.authService.setUser(convertJWTToUser(token));
19
+ } else {
20
+ this.router.navigate([this.config.loginPage]);
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,15 @@
1
+ import { inject, Injectable } from '@angular/core';
2
+ import { SECURITY_CONTEXT_TOKEN_PROVIDER } from '../provider';
3
+ import { AuthContextService } from './auth-context.service';
4
+ import { LocalStorageTokenProvider } from './local-storage-token-provider';
5
+
6
+ @Injectable()
7
+ export class LogoutService {
8
+ private readonly tokenProvider = inject<LocalStorageTokenProvider>(SECURITY_CONTEXT_TOKEN_PROVIDER);
9
+ private readonly authService = inject(AuthContextService);
10
+
11
+ logout(): void {
12
+ this.tokenProvider.removeToken();
13
+ this.authService.setUser(undefined);
14
+ }
15
+ }
@@ -1,5 +1,5 @@
1
1
  import { Observable } from "rxjs";
2
2
 
3
3
  export interface TokenProvider {
4
- getToken(): Observable<string | undefined>;
5
- }
4
+ getToken(): Observable<string | undefined>;
5
+ }
@@ -1,8 +1,7 @@
1
1
  import { createAction, props } from '@ngrx/store';
2
2
  import { AuthenticatedUser } from '../models';
3
3
 
4
- // sets or unsets the user
5
4
  export const setUser = createAction(
6
5
  '[Auth] Set User',
7
6
  props<{ user: AuthenticatedUser | undefined }>()
8
- );
7
+ );