@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 +11 -9
- package/src/lib/directives/has-permission.directive.ts +3 -7
- package/src/lib/directives/has-role.directive.ts +3 -7
- package/src/lib/guards/auth.guard.ts +3 -7
- package/src/lib/models/authenticated-user.model.ts +3 -2
- package/src/lib/models/config.model.ts +3 -1
- package/src/lib/models/functions.ts +18 -7
- package/src/lib/provider.ts +3 -6
- package/src/lib/services/index.ts +3 -1
- package/src/lib/services/local-storage-token-provider.ts +23 -0
- package/src/lib/services/login.service.ts +23 -0
- package/src/lib/services/logout.service.ts +15 -0
- package/src/lib/services/token-provider.ts +2 -2
- package/src/lib/state/actions.ts +1 -2
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@verisoft/security-core",
|
|
3
|
-
"version": "18.
|
|
4
|
-
"peerDependencies": {
|
|
5
|
-
"@angular/
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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))
|
|
@@ -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
|
|
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
|
package/src/lib/provider.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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: [
|
|
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
|
+
}
|
package/src/lib/state/actions.ts
CHANGED