feat(login): Added auth-guard to avoid unauthorized access

Squashed commit of the following:

commit c8f20f6ff0dee2257a4191d8e6771ed2fc364326
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Jun 30 12:04:40 2021 +0200

    Removed current from currentUser and currentToken/currentExpiration

commit fef6b046861efe8cfacb5b5b1e9dbb86bff42336
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Jun 30 10:42:27 2021 +0200

    Fixed some tests

commit f357546d3a61ad66d804a7cb36807985c8435974
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Jun 30 09:41:47 2021 +0200

    Fixed linting

commit 85fdbaed8d922bec235e4987cc34464c1419a093
Merge: c93dd92 c06452d
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Jun 30 09:29:55 2021 +0200

    Merged develop and resolved conflicts

commit c93dd925b06a0b8a0361a687165e9c3954e2050b
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Jun 30 07:43:57 2021 +0200

    Moved some components to shared folder

commit aa1cc2b6240236149b0367363d4175fbdacf94dc
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Wed Jun 30 07:32:28 2021 +0200

    Removed comments and some unused code

commit 7b83eb9d9d368b7466189ab3588fa91697db49c0
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Jun 29 14:56:02 2021 +0200

    Login-flow now works locally and against API

commit dab5a76f2b6e24447d85e237233053a3f23b1b39
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Tue Jun 29 12:50:24 2021 +0200

    Adjusted login-functionality to use a guard
This commit is contained in:
Erik Tiekstra
2021-06-30 12:06:05 +02:00
parent c06452dbbd
commit e9159bcbc4
81 changed files with 570 additions and 575 deletions

View File

@@ -104,8 +104,8 @@
"browserTarget": "dafa-web:build:production"
},
"api": {
"proxyConfig": "./config/proxy.conf.api.json",
"browserTarget": "dafa-web:build:api"
"browserTarget": "dafa-web:build:api",
"proxyConfig": "./config/proxy.conf.api.json"
}
}
},

View File

@@ -1,56 +1,60 @@
import { NgModule } from '@angular/core';
import { ExtraOptions, RouterModule, Routes } from '@angular/router';
import { environment } from '@dafa-environment';
import { AuthGuard } from '@dafa-guards/auth.guard';
const routes: Routes = [
{
path: '',
data: { title: '' },
loadChildren: () => import('./pages/start/start.module').then(m => m.StartModule),
canActivate: [AuthGuard],
},
{
path: 'administration',
data: { title: 'Administration' },
loadChildren: () => import('./pages/administration/administration.module').then(m => m.AdministrationModule),
canActivate: [AuthGuard],
},
{
path: 'deltagare',
data: { title: 'Deltagare' },
loadChildren: () => import('./pages/participants/participants.module').then(m => m.ParticipantsModule),
canActivate: [AuthGuard],
},
{
path: 'avrop',
data: { title: 'Avrop' },
loadChildren: () => import('./pages/avrop/avrop.module').then(m => m.AvropModule),
canActivate: [AuthGuard],
},
{
path: 'meddelanden',
data: { title: 'Meddelanden' },
loadChildren: () => import('./pages/messages/messages.module').then(m => m.MessagesModule),
canActivate: [AuthGuard],
},
{
path: 'statistik',
data: { title: 'Statistik' },
loadChildren: () => import('./pages/statistics/statistics.module').then(m => m.StatisticsModule),
canActivate: [AuthGuard],
},
{
path: 'installningar',
data: { title: 'Inställningar' },
loadChildren: () => import('./pages/settings/settings.module').then(m => m.SettingsModule),
canActivate: [AuthGuard],
},
{
path: 'releases',
data: { title: 'Releases' },
loadChildren: () => import('./pages/releases/releases.module').then(m => m.ReleasesModule),
},
{
path: 'ciam-landing',
data: { title: 'Ciam landing page' },
loadChildren: () => import('./pages/ciam-landing/ciam-landing.module').then(m => m.CiamLandingModule),
canActivate: [AuthGuard],
},
{
path: 'logout',
data: { title: 'Ciam landing page' },
data: { title: 'Logga ut' },
loadChildren: () => import('./pages/logout/logout.module').then(m => m.LogoutModule),
},
];
@@ -67,6 +71,7 @@ routes.push({
path: '**',
data: { title: 'Sidan hittas inte' },
loadChildren: () => import('./pages/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule),
canActivate: [AuthGuard],
});
const options: ExtraOptions = {

View File

@@ -1,2 +1,3 @@
<router-outlet></router-outlet>
<router-outlet></router-outlet>
<dafa-toast-list></dafa-toast-list>

View File

@@ -3,16 +3,13 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { NavigationModule } from './components/navigation/navigation.module';
import { SidebarModule } from './components/sidebar/sidebar.module';
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AppComponent],
imports: [RouterTestingModule, HttpClientTestingModule, SkipToContentModule, NavigationModule, SidebarModule],
imports: [RouterTestingModule, HttpClientTestingModule],
}).compileComponents();
});

View File

@@ -6,6 +6,4 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
}
export class AppComponent {}

View File

@@ -1,42 +1,24 @@
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { ErrorHandler, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AuthGuard } from '@dafa-guards/auth.guard';
import { CustomErrorHandler } from '@dafa-interceptors/custom-error-handler.module';
import { MarkdownModule } from 'ngx-markdown';
import { AuthInterceptor } from '@dafa-services/api/auth.interceptor';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FooterModule } from './components/footer/footer.module';
import { NavigationModule } from './components/navigation/navigation.module';
import { SidebarModule } from './components/sidebar/sidebar.module';
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
import { ToastListModule } from './components/toast-list/toast-list.module';
import { AuthInterceptor } from '@dafa-services/api/auth.interceptor';
import { AvropModule } from './pages/avrop/avrop.module';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
RouterModule,
SkipToContentModule,
NavigationModule,
SidebarModule,
ToastListModule,
FooterModule,
MarkdownModule.forRoot({ loader: HttpClient }),
DigiNgNavigationBreadcrumbsModule,
AvropModule,
],
imports: [BrowserModule, HttpClientModule, AppRoutingModule, ToastListModule, AvropModule],
providers: [
{
provide: ErrorHandler,
useClass: CustomErrorHandler,
},
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
AuthGuard,
],
bootstrap: [AppComponent],
})

View File

@@ -1,26 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoggedInShellComponent } from './logged-in-shell.component';
import { SkipToContentModule } from '../skip-to-content/skip-to-content.module';
import { NavigationModule } from '../navigation/navigation.module';
import { SidebarModule } from '../sidebar/sidebar.module';
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { FooterModule } from '../footer/footer.module';
import { ToastListModule } from '../toast-list/toast-list.module';
import { RouterModule } from '@angular/router';
@NgModule({
imports: [
RouterModule,
CommonModule,
SkipToContentModule,
NavigationModule,
SidebarModule,
DigiNgNavigationBreadcrumbsModule,
FooterModule,
ToastListModule,
],
declarations: [LoggedInShellComponent],
exports: [LoggedInShellComponent],
})
export class LoggedInShellModule {}

View File

@@ -1,15 +1,9 @@
export interface AuthenticationResult {
idToken: string;
expiresIn: number;
}
export interface AuthenticationApiResponse {
data: AuthenticationApiResponseData;
}
export interface AuthenticationApiResponseData {
id: string;
access_token: string;
scope: string;
@@ -18,12 +12,10 @@ export interface AuthenticationApiResponseData {
expires_in: number;
}
export function mapAuthApiResponseToAuthenticationResult(data: AuthenticationApiResponseData): AuthenticationResult {
const {
id_token,
expires_in } = data;
export function mapAuthApiResponseToAuthenticationResult(data: AuthenticationApiResponse): AuthenticationResult {
const { id_token, expires_in } = data;
return {
idToken: id_token,
expiresIn: expires_in
expiresIn: expires_in,
};
}

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { environment } from '@dafa-environment';
import { AuthenticationService } from '@dafa-services/api/authentication.service';
import { Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authenticationService: AuthenticationService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
return this.authenticationService.isLoggedIn$.pipe(
switchMap(loggedIn => {
if (loggedIn) {
return of(true);
} else if (route.queryParams.code) {
return this.authenticationService.login$(route.queryParams.code).pipe(map(result => !!result));
}
if (environment.environment === 'local') {
void this.router.navigateByUrl(environment.loginUrl);
} else {
document.location.href = `${environment.loginUrl}&client_id=${environment.clientId}&redirect_uri=${environment.redirectUri}`;
}
return of(false);
})
);
}
}

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell>
<dafa-layout>
<section class="employee-card">
<digi-typography *ngIf="detailedEmployeeData$ | async as detailedEmployeeData; else loadingRef">
<div class="employee-card__editcontainer">
@@ -90,4 +90,4 @@
<span class="dafa__a11y-sr-only">Info saknas</span>
</dd>
</ng-template>
</dafa-logged-in-shell>
</dafa-layout>

View File

@@ -3,10 +3,10 @@ import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HideTextModule } from '@dafa-shared/components/hide-text/hide-text.module';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { LocalDatePipeModule } from '@dafa-shared/pipes/local-date/local-date.module';
import { EmployeeCardComponent } from './employee-card.component';
import { LoggedInShellModule } from '../../../../components/logged-in-shell/logged-in-shell.module';
import { HideTextModule } from '@dafa-shared/components/hide-text/hide-text.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -14,11 +14,11 @@ import { HideTextModule } from '@dafa-shared/components/hide-text/hide-text.modu
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: EmployeeCardComponent }]),
LayoutModule,
DigiNgSkeletonBaseModule,
DigiNgLayoutExpansionPanelModule,
LocalDatePipeModule,
HideTextModule,
LoggedInShellModule
],
})
export class EmployeeCardModule {}

View File

@@ -1,158 +1,164 @@
<dafa-logged-in-shell>
<dafa-layout>
<section class="employee-form">
<digi-typography>
<h1>Skapa nytt konto</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam magna neque, interdum vel massa eget, condimentum
rutrum velit. Sed vitae ullamcorper sem. Aliquam malesuada nunc sed purus mollis scelerisque. Curabitur bibendum
leo quis ante porttitor tincidunt. Nam tincidunt imperdiet tortor eu suscipit. Maecenas ut dui est.
</p>
</digi-typography>
<form [formGroup]="formGroup" (ngSubmit)="submitForm()">
<digi-form-error-list
class="employee-form__error-list"
*ngIf="formGroup.invalid && submitted && formErrors.length"
af-heading="Felmeddelanden"
>
<a *ngFor="let error of formErrors" [routerLink]="" [fragment]="'employee-form-' + error.id">{{
error.message
}}</a>
</digi-form-error-list>
<digi-typography>
<h1>Skapa nytt konto</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam magna neque, interdum vel massa eget, condimentum
rutrum velit. Sed vitae ullamcorper sem. Aliquam malesuada nunc sed purus mollis scelerisque. Curabitur bibendum
leo quis ante porttitor tincidunt. Nam tincidunt imperdiet tortor eu suscipit. Maecenas ut dui est.
</p>
</digi-typography>
<form [formGroup]="formGroup" (ngSubmit)="submitForm()">
<digi-form-error-list
class="employee-form__error-list"
*ngIf="formGroup.invalid && submitted && formErrors.length"
af-heading="Felmeddelanden"
>
<a *ngFor="let error of formErrors" [routerLink]="" [fragment]="'employee-form-' + error.id">{{
error.message
}}</a>
</digi-form-error-list>
<div class="employee-form__block">
<digi-typography>
<h2>Personuppgifter</h2>
</digi-typography>
<digi-ng-form-input
afId="employee-form-firstName"
class="employee-form__input"
formControlName="firstName"
afLabel="Förnamn"
afInvalidMessage="Förnamn är obligatoriskt"
[afDisableValidStyle]="true"
[afInvalid]="firstNameControl.invalid && firstNameControl.dirty"
></digi-ng-form-input>
<digi-ng-form-input
afId="employee-form-lastName"
class="employee-form__input"
formControlName="lastName"
afLabel="Efternamn"
afInvalidMessage="Efternamn är obligatoriskt"
[afDisableValidStyle]="true"
[afInvalid]="lastNameControl.invalid && lastNameControl.dirty"
></digi-ng-form-input>
<digi-ng-form-input
afId="employee-form-ssn"
class="employee-form__input"
formControlName="ssn"
afLabel="Personnummer"
[afInvalidMessage]="ssnControl.errors?.message || ''"
[afDisableValidStyle]="true"
[afInvalid]="ssnControl.invalid && ssnControl.dirty"
></digi-ng-form-input>
</div>
<div class="employee-form__block" *ngIf="services$ | async as services">
<fieldset class="employee-form__fieldset">
<div class="employee-form__block">
<digi-typography>
<legend>Tjänster</legend>
<h2>Personuppgifter</h2>
</digi-typography>
<digi-ng-form-input
afId="employee-form-firstName"
class="employee-form__input"
formControlName="firstName"
afLabel="Förnamn"
afInvalidMessage="Förnamn är obligatoriskt"
[afDisableValidStyle]="true"
[afInvalid]="firstNameControl.invalid && firstNameControl.dirty"
></digi-ng-form-input>
<digi-ng-form-input
afId="employee-form-lastName"
class="employee-form__input"
formControlName="lastName"
afLabel="Efternamn"
afInvalidMessage="Efternamn är obligatoriskt"
[afDisableValidStyle]="true"
[afInvalid]="lastNameControl.invalid && lastNameControl.dirty"
></digi-ng-form-input>
<digi-ng-form-input
afId="employee-form-ssn"
class="employee-form__input"
formControlName="ssn"
afLabel="Personnummer"
[afInvalidMessage]="ssnControl.errors?.message || ''"
[afDisableValidStyle]="true"
[afInvalid]="ssnControl.invalid && ssnControl.dirty"
></digi-ng-form-input>
</div>
<div class="employee-form__block" *ngIf="services$ | async as services">
<fieldset class="employee-form__fieldset">
<digi-typography>
<legend>Tjänster</legend>
</digi-typography>
<ul class="employee-form__services">
<li *ngFor="let service of services; let first = first" class="employee-form__service-item">
<digi-form-checkbox
[afId]="(first && 'employee-form-services') || undefined"
af-variation="primary"
[afValidation]="servicesControl.invalid && servicesControl.dirty && 'error'"
[afLabel]="service.name"
[afValue]="service.id"
[afChecked]="servicesControl.value.includes(service)"
(afOnChange)="toggleService(service, $event.detail.target.checked)"
></digi-form-checkbox>
</li>
</ul>
<digi-form-validation-message
class="employee-form__validation-message"
*ngIf="servicesControl.invalid && servicesControl.dirty"
af-variation="error"
<ul class="employee-form__services">
<li *ngFor="let service of services; let first = first" class="employee-form__service-item">
<digi-form-checkbox
[afId]="(first && 'employee-form-services') || undefined"
af-variation="primary"
[afValidation]="servicesControl.invalid && servicesControl.dirty && 'error'"
[afLabel]="service.name"
[afValue]="service.id"
[afChecked]="servicesControl.value.includes(service)"
(afOnChange)="toggleService(service, $event.detail.target.checked)"
></digi-form-checkbox>
</li>
</ul>
<digi-form-validation-message
class="employee-form__validation-message"
*ngIf="servicesControl.invalid && servicesControl.dirty"
af-variation="error"
>
{{ servicesControl.errors.message }}
</digi-form-validation-message>
</fieldset>
</div>
<div class="employee-form__block" *ngIf="authorizations$ | async as authorizations">
<fieldset class="employee-form__fieldset">
<digi-typography>
<legend>Tilldela behörigheter</legend>
</digi-typography>
<ul class="employee-form__authorizations">
<li
*ngFor="let authorization of authorizations; let first = first"
class="employee-form__authorization-item"
>
<digi-form-checkbox
class="employee-form__digi-checkbox"
[afId]="(first && 'employee-form-authorizations') || undefined"
af-variation="primary"
[afValidation]="authorizationsControl.invalid && authorizationsControl.dirty && 'error'"
[afLabel]="authorization.name"
[afValue]="authorization.id"
[afChecked]="authorizationsControl.value.includes(authorization)"
(afOnChange)="toggleAuthorization(authorization, $event.detail.target.checked)"
></digi-form-checkbox>
<digi-button
af-variation="secondary"
[afAriaLabel]="'Läs mer om ' + authorization.name"
af-size="s"
class="employee-form__read-more"
(afOnClick)="openDialog(true, authorization.name)"
>
Läs mer
</digi-button>
</li>
</ul>
<digi-form-validation-message
class="employee-form__validation-message"
*ngIf="authorizationsControl.invalid && authorizationsControl.dirty"
af-variation="error"
>
{{ authorizationsControl.errors.message }}
</digi-form-validation-message>
</fieldset>
</div>
<div class="employee-form__footer">
<digi-button af-type="reset" af-variation="secondary" (afOnClick)="resetForm($event.detail)"
>Avbryt</digi-button
>
{{ servicesControl.errors.message }}
</digi-form-validation-message>
</fieldset>
</div>
<digi-button af-type="submit">Registrera konto</digi-button>
</div>
<div class="employee-form__block" *ngIf="authorizations$ | async as authorizations">
<fieldset class="employee-form__fieldset">
<digi-typography>
<legend>Tilldela behörigheter</legend>
</digi-typography>
<!-- Modal/ Dialog window -->
<digi-ng-dialog
[afActive]="toggleDialog"
(afOnInactive)="openDialog(false)"
(afOnPrimaryClick)="openDialog(false)"
[afHeading]="modalAuthInfo.name"
afHeadingLevel="h3"
afPrimaryButtonText="Stäng"
>
<p>
Behörigheten passar personer som arbetar nära deltagare. Behörigheten kan användas av exempelvis handledare,
coacher, studie- och yrkesvägledare, lärare eller annan roll som behöver kunna se information om deltager,
kontakta deltagare, planera aktiviteter med deltagre och hantera rapporter för deltagre.
</p>
<ul class="employee-form__authorizations">
<li *ngFor="let authorization of authorizations; let first = first"
class="employee-form__authorization-item">
<digi-form-checkbox
class="employee-form__digi-checkbox"
[afId]="(first && 'employee-form-authorizations') || undefined"
af-variation="primary"
[afValidation]="authorizationsControl.invalid && authorizationsControl.dirty && 'error'"
[afLabel]="authorization.name"
[afValue]="authorization.id"
[afChecked]="authorizationsControl.value.includes(authorization)"
(afOnChange)="toggleAuthorization(authorization, $event.detail.target.checked)"
></digi-form-checkbox>
<digi-button af-variation="secondary" [afAriaLabel]="'Läs mer om ' + authorization.name"
af-size="s" class="employee-form__read-more"
(afOnClick)="openDialog(true, authorization.name)">
Läs mer
</digi-button>
</li>
</ul>
<digi-form-validation-message
class="employee-form__validation-message"
*ngIf="authorizationsControl.invalid && authorizationsControl.dirty"
af-variation="error"
>
{{ authorizationsControl.errors.message }}
</digi-form-validation-message>
</fieldset>
</div>
<p>Behörigheten ger tillgång till och utföra aktiviteter i följande funktioner i systemet:</p>
<div class="employee-form__footer">
<digi-button af-type="reset" af-variation="secondary" (afOnClick)="resetForm($event.detail)">Avbryt</digi-button>
<digi-button af-type="submit">Registrera konto</digi-button>
</div>
<!-- Modal/ Dialog window -->
<digi-ng-dialog
[afActive]="toggleDialog"
(afOnInactive)="openDialog(false)"
(afOnPrimaryClick)="openDialog(false)"
[afHeading]="modalAuthInfo.name"
afHeadingLevel="h3"
afPrimaryButtonText="Stäng">
<p>
Behörigheten passar personer som arbetar nära deltagare.
Behörigheten kan användas av exempelvis handledare, coacher, studie- och yrkesvägledare,
lärare eller annan roll som behöver kunna se information om deltager, kontakta deltagare,
planera aktiviteter med deltagre och hantera rapporter för deltagre.
</p>
<p>
Behörigheten ger tillgång till och utföra aktiviteter i följande funktioner i systemet:
</p>
<p>
- Deltagarlista <br/>
- Information om deltagare <br/>
- Resultatrapporter <br/>
- Slutredovisning <br/>
- Informativ rapport <br/>
- Skicka välkomstbrev * <br/>
- Planera deltagares aktiviteter <br/>
- Deltagares schema <br/>
- Avvikelserapporter <br/>
- Närvaro- och frånvarorapporter <br/><br/>
</p>
</digi-ng-dialog>
</form>
</section>
</dafa-logged-in-shell>
<p>
- Deltagarlista <br />
- Information om deltagare <br />
- Resultatrapporter <br />
- Slutredovisning <br />
- Informativ rapport <br />
- Skicka välkomstbrev * <br />
- Planera deltagares aktiviteter <br />
- Deltagares schema <br />
- Avvikelserapporter <br />
- Närvaro- och frånvarorapporter <br /><br />
</p>
</digi-ng-dialog>
</form>
</section>
</dafa-layout>

View File

@@ -1,3 +1,4 @@
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
import { DigiNgFormCheckboxModule } from '@af/digi-ng/_form/form-checkbox';
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
import { DigiNgFormInputModule } from '@af/digi-ng/_form/form-input';
@@ -8,10 +9,9 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { LocalDatePipeModule } from '@dafa-shared/pipes/local-date/local-date.module';
import { EmployeeFormComponent } from './employee-form.component';
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
import { LoggedInShellModule } from '../../../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -19,6 +19,7 @@ import { LoggedInShellModule } from '../../../../components/logged-in-shell/logg
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: EmployeeFormComponent }]),
LayoutModule,
ReactiveFormsModule,
LocalDatePipeModule,
DigiNgFormInputModule,
@@ -28,7 +29,6 @@ import { LoggedInShellModule } from '../../../../components/logged-in-shell/logg
DigiNgPopoverModule,
DigiNgFormCheckboxModule,
DigiNgDialogModule,
LoggedInShellModule
]
],
})
export class EmployeeFormModule {}

View File

@@ -4,9 +4,13 @@
<thead>
<tr>
<th scope="col" class="employees-list__column-head" *ngFor="let column of columnHeaders">
<button class="employees-list__sort-button" [id]="'sort-button-' + column" (click)="handleSort(column.key)">
<button
class="employees-list__sort-button"
[attr.id]="'sort-button-' + column.key"
(click)="handleSort(column.key)"
>
{{column.label}}
<ng-container *ngIf="sort.key === column">
<ng-container *ngIf="sort.key === column.key">
<digi-icon-caret-up
class="employees-list__sort-icon"
*ngIf="sort.order === orderType.ASC"

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell>
<dafa-layout>
<section class="employees">
<digi-typography>
<h1>Personal</h1>
@@ -37,4 +37,4 @@
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base>
</ng-template>
</section>
</dafa-logged-in-shell>
</dafa-layout>

View File

@@ -5,9 +5,9 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { EmployeesListModule } from './components/employees-list/employees-list.module';
import { EmployeesComponent } from './employees.component';
import { LoggedInShellModule } from '../../../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -15,12 +15,12 @@ import { LoggedInShellModule } from '../../../../components/logged-in-shell/logg
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: EmployeesComponent }]),
LayoutModule,
DigiNgLinkInternalModule,
DigiNgSkeletonBaseModule,
EmployeesListModule,
DigiNgLinkButtonModule,
FormsModule,
LoggedInShellModule
]
],
})
export class EmployeesModule {}

View File

@@ -1,22 +1,16 @@
<dafa-logged-in-shell>
<section class="call-off" *ngIf="currentStep$ | async; let currentStep; else loadingRef">
<dafa-layout>
<section class="call-off" *ngIf="currentStep$ | async; let currentStep; else: loadingRef">
<digi-typography>
<h2>Välj deltagare att tilldela</h2>
<p>Steg {{ currentStep }} av {{ steps }}:</p>
</digi-typography>
<digi-ng-progress-progressbar
[afSteps]="steps"
afAriaLabel="An aria label"
[afActiveStep]="currentStep">
<digi-ng-progress-progressbar [afSteps]="steps" afAriaLabel="An aria label" [afActiveStep]="currentStep">
</digi-ng-progress-progressbar>
<div class="" style="height:300px; padding: 30px 0;">
<div class="" style="height: 300px; padding: 30px 0">
<ng-container *ngIf="currentStep == 4">
<h2>Avropet är sparat</h2>
<digi-button
af-size="m"
class="employee-form__read-more"
(afOnClick)="goToStep1()">
<digi-button af-size="m" class="employee-form__read-more" (afOnClick)="goToStep1()">
Tillbaka till nya deltagare
</digi-button>
</ng-container>
@@ -28,83 +22,77 @@
<h2>Vänligen bekräfta</h2>
</ng-container>
<ng-container *ngIf="currentStep < 4">
<dafa-avrop-table
[selectableDeltagareList]="selectableDeltagareList$ | async"
[selectedDeltagareListInput]="selectedDeltagareList$ | async"
[isLocked]="deltagareListIsLocked$ | async"
(changedSelectedDeltagareList)="updateSelectedDeltagareList($event)"
[handledare]="selectedHandledare$ | async"
[handledareConfirmed]="handledareConfirmed$ | async"
></dafa-avrop-table>
<dafa-avrop-table
[selectableDeltagareList]="selectableDeltagareList$ | async"
[selectedDeltagareListInput]="selectedDeltagareList$ | async"
[isLocked]="deltagareListIsLocked$ | async"
(changedSelectedDeltagareList)="updateSelectedDeltagareList($event)"
[handledare]="selectedHandledare$ | async"
[handledareConfirmed]="handledareConfirmed$ | async"
></dafa-avrop-table>
</ng-container>
<ng-container *ngIf="currentStep == 1">
<digi-button
af-size="m"
class="employee-form__read-more"
(afOnClick)="lockSelectedDeltagare()">
<digi-button af-size="m" class="employee-form__read-more" (afOnClick)="lockSelectedDeltagare()">
Lås deltagare
</digi-button>
</ng-container>
<ng-container *ngIf="currentStep == 2">
<h2>Välj handledare</h2>
<ng-container *ngIf="selectableHandledareList$ | async; let selectableHandledareList; else loadingRefSmall">
<select [value]="(selectedHandledare$ | async)?.id ? (selectedHandledare$ | async)?.id : ''" (change)="changeHandledare($event)">
<option disabled value="" >Välj handledare</option>
<ng-container *ngIf="selectableHandledareList$ | async; let selectableHandledareList; else: loadingRefSmall">
<select
[value]="(selectedHandledare$ | async)?.id ? (selectedHandledare$ | async)?.id : ''"
(change)="changeHandledare($event)"
>
<option disabled value="">Välj handledare</option>
<option
*ngFor="let selectableHandledare of selectableHandledareList"
[value]="selectableHandledare?.id"
>{{selectableHandledare?.fullName}}</option>
<option *ngFor="let selectableHandledare of selectableHandledareList" [value]="selectableHandledare?.id">
{{ selectableHandledare?.fullName }}
</option>
</select>
<span *ngIf="selectableHandledareList.length === 0">Inga handledare har behörighet till alla markerade deltagare</span>
<span *ngIf="selectableHandledareList.length === 0"
>Inga handledare har behörighet till alla markerade deltagare</span
>
</ng-container>
<br><br>
<digi-button af-variation="secondary"
af-size="m"
class="employee-form__read-more"
(afOnClick)="unlockSelectedDeltagare()">
Tillbaka
</digi-button>
<br /><br />
<digi-button
af-variation="secondary"
af-size="m"
class="employee-form__read-more"
(afOnClick)="confirmHandledare()">
(afOnClick)="unlockSelectedDeltagare()"
>
Tillbaka
</digi-button>
<digi-button af-size="m" class="employee-form__read-more" (afOnClick)="confirmHandledare()">
Tilldela
</digi-button>
</ng-container>
<div *ngIf="currentStep == 3">
<br><br>
<digi-button af-variation="secondary"
af-size="m"
class="employee-form__read-more"
(afOnClick)="unconfirmHandledare()">
Tillbaka
</digi-button>
<br /><br />
<digi-button
af-variation="secondary"
af-size="m"
class="employee-form__read-more"
(afOnClick)="save()">
Spara avrop
(afOnClick)="unconfirmHandledare()"
>
Tillbaka
</digi-button>
<digi-button af-size="m" class="employee-form__read-more" (afOnClick)="save()"> Spara avrop </digi-button>
</div>
</div>
</section>
</dafa-logged-in-shell>
<ng-template #loadingRef>
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base>
</ng-template>
<ng-template #loadingRef>
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base>
</ng-template>
<ng-template #loadingRefSmall>
<digi-icon-spinner af-title="Laddar innehåll"></digi-icon-spinner>
</ng-template>
<ng-template #loadingRefSmall>
<digi-icon-spinner af-title="Laddar innehåll"></digi-icon-spinner>
</ng-template>
<hr>
<hr />
</dafa-layout>

View File

@@ -1,22 +1,28 @@
import { DigiNgProgressProgressbarModule } from '@af/digi-ng/_progress/progressbar';
import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AvropComponent } from './avrop.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
import { DigiNgProgressProgressbarModule } from '@af/digi-ng/_progress/progressbar';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { AvropFiltersComponent } from './avrop-filters/avrop-filters.component';
import { AvropTableComponent } from './avrop-table/avrop-table.component';
import { AvropTableRowComponent } from './avrop-table/avrop-table-row/avrop-table-row.component';
import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
import { TemporaryFilterComponent } from './avrop-filters/temporary-filter/temporary-filter.component';
import { AvropTableRowComponent } from './avrop-table/avrop-table-row/avrop-table-row.component';
import { AvropTableComponent } from './avrop-table/avrop-table.component';
import { AvropComponent } from './avrop.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AvropComponent, AvropFiltersComponent, AvropTableComponent, AvropTableRowComponent, TemporaryFilterComponent],
declarations: [
AvropComponent,
AvropFiltersComponent,
AvropTableComponent,
AvropTableRowComponent,
TemporaryFilterComponent,
],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: AvropComponent }]),
LoggedInShellModule,
LayoutModule,
DigiNgProgressProgressbarModule,
DigiNgSkeletonBaseModule,
],

View File

@@ -1,7 +0,0 @@
<digi-typography>
<section class="releases">
<h1>Ciam landing</h1>
<p>Redirecting to /......</p>
</section>
</digi-typography>

View File

@@ -1,37 +0,0 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { first, map, switchMap } from 'rxjs/operators';
import { AuthenticationService } from '@dafa-services/api/authentication.service';
@Component({
selector: 'dafa-ciam-landing',
templateUrl: './ciam-landing.component.html',
styleUrls: ['./ciam-landing.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CiamLandingComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private router: Router,
private authenticationService: AuthenticationService
) {}
ngOnInit(): void {
this.route.queryParams
.pipe(
first(),
map(({ code }) => {
if (!code) {
throw new Error('Expected CIAM to return "code" in queryparams.');
}
return code as string;
}),
switchMap(code => {
return this.authenticationService.login$(code);
})
)
.subscribe(() => {
void this.router.navigateByUrl('/');
});
}
}

View File

@@ -1,16 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CiamLandingComponent } from './ciam-landing.component';
import { DigiNgButtonModule } from '@af/digi-ng/_button/button';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [CiamLandingComponent],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: CiamLandingComponent }]),
DigiNgButtonModule
]
})
export class CiamLandingModule {}

View File

@@ -1,8 +1,8 @@
import { DigiNgButtonModule } from '@af/digi-ng/_button/button';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LogoutComponent } from './logout.component';
import { DigiNgButtonModule } from '@af/digi-ng/_button/button';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],

View File

@@ -1,4 +1,3 @@
<dafa-logged-in-shell>
<section class="messages">Meddelanden funkar!</section>
</dafa-logged-in-shell>
<dafa-layout>
<section class="messages">Meddelanden funkar!</section>
</dafa-layout>

View File

@@ -1,11 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { MessagesComponent } from './messages.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
declarations: [MessagesComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: MessagesComponent }]), LoggedInShellModule]
imports: [CommonModule, RouterModule.forChild([{ path: '', component: MessagesComponent }]), LayoutModule],
})
export class MessagesModule {}

View File

@@ -1,8 +1,11 @@
<digi-typography>
<section class="login__wrapper">
<section class="mock-login">
<h1>Mock login</h1>
<p>Simulera att man loggar in och blir redirectad till /ciam-landing med en Authorization-code som sedan används för att hämta authentication-token:</p>
<digi-ng-button [routerLink]="'/ciam-landing'" [queryParams]="{code: 'auth_code_from_CIAM_with_all_permissions'}">
<p>
Simulera att man loggar in och blir redirectad till startsidan med en Authorization-code som sedan används för att
hämta authentication-token:
</p>
<digi-ng-button routerLink="/" [queryParams]="{ code: 'auth_code_from_CIAM_with_all_permissions' }">
Logga in med fullständiga rättigheter
</digi-ng-button>
</section>

View File

@@ -1,3 +1,3 @@
.login__wrapper {
.mock-login {
margin: 5rem;
}

View File

@@ -1,8 +1,8 @@
import { DigiNgButtonModule } from '@af/digi-ng/_button/button';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MockLoginComponent } from './mock-login.component';
import { DigiNgButtonModule } from '@af/digi-ng/_button/button';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],

View File

@@ -1,12 +1,12 @@
<dafa-logged-in-shell>
<digi-typography>
<section class="page-not-found">
<h1>Oj då! Vi kan inte hitta sidan.</h1>
<p>Det kan bero på att länken du använder är felaktig eller att sidan inte längre finns.</p>
<a class="dafa__link dafa__link--with-icon dafa__link--ignore-visited" routerLink="/">
<digi-icon-arrow-left class="dafa__digi-icon"></digi-icon-arrow-left>
Gå tillbaka till startsidan
</a>
</section>
</digi-typography>
</dafa-logged-in-shell>
<dafa-layout>
<digi-typography>
<section class="page-not-found">
<h1>Oj då! Vi kan inte hitta sidan.</h1>
<p>Det kan bero på att länken du använder är felaktig eller att sidan inte längre finns.</p>
<a class="dafa__link dafa__link--with-icon dafa__link--ignore-visited" routerLink="/">
<digi-icon-arrow-left class="dafa__digi-icon"></digi-icon-arrow-left>
Gå tillbaka till startsidan
</a>
</section>
</digi-typography>
</dafa-layout>

View File

@@ -1,12 +1,12 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { PageNotFoundComponent } from './page-not-found.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [PageNotFoundComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: PageNotFoundComponent }]), LoggedInShellModule]
imports: [CommonModule, RouterModule.forChild([{ path: '', component: PageNotFoundComponent }]), LayoutModule],
})
export class PageNotFoundModule {}

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell>
<dafa-layout>
<section class="participant-card">
<digi-typography *ngIf="detailedParticipantData$ | async as detailedParticipantData; else loadingRef">
<header class="participant-card__header">
@@ -54,4 +54,4 @@
<span class="dafa__a11y-sr-only">Info saknas</span>
</dd>
</ng-template>
</dafa-logged-in-shell>
</dafa-layout>

View File

@@ -4,8 +4,8 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { BackLinkModule } from '@dafa-shared/components/back-link/back-link.module';
import { IconModule } from '@dafa-shared/components/icon/icon.module';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { ParticipantCardComponent } from './participant-card.component';
import { LoggedInShellModule } from '../../../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -13,7 +13,7 @@ import { LoggedInShellModule } from '../../../../components/logged-in-shell/logg
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: ParticipantCardComponent }]),
LoggedInShellModule,
LayoutModule,
DigiNgLinkInternalModule,
IconModule,
BackLinkModule,

View File

@@ -1,44 +1,44 @@
<dafa-logged-in-shell>
<section class="participants">
<digi-typography>
<h1>Mina deltagare</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam magna neque, interdum vel massa eget, condimentum
rutrum velit. Sed vitae ullamcorper sem. Aliquam malesuada nunc sed purus mollis scelerisque. Curabitur bibendum
leo quis ante porttitor tincidunt. Nam tincidunt imperdiet tortor eu suscipit. Maecenas ut dui est.
</p>
</digi-typography>
<dafa-layout>
<section class="participants">
<digi-typography>
<h1>Mina deltagare</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam magna neque, interdum vel massa eget, condimentum
rutrum velit. Sed vitae ullamcorper sem. Aliquam malesuada nunc sed purus mollis scelerisque. Curabitur bibendum
leo quis ante porttitor tincidunt. Nam tincidunt imperdiet tortor eu suscipit. Maecenas ut dui est.
</p>
</digi-typography>
<form class="participants__search-wrapper" (ngSubmit)="handleSearchSubmit()">
<digi-form-input-search
af-label="Sök deltagare"
af-label-description="Sök på namn eller ärendenummer"
(afOnInput)="handleSearchInput($event)"
></digi-form-input-search>
</form>
<form class="participants__search-wrapper" (ngSubmit)="handleSearchSubmit()">
<digi-form-input-search
af-label="Sök deltagare"
af-label-description="Sök på namn eller ärendenummer"
(afOnInput)="handleSearchInput($event)"
></digi-form-input-search>
</form>
<section class="participants__list">
<h2>Pågående tjänst</h2>
<dafa-participants-list
*ngIf="activeParticipants$ | async as participants; else loadingRef"
[participants]="participants"
[sortBy]="activeParticipantsSortBy$ | async"
(sorted)="handleActiveParticipantsSort($event)"
></dafa-participants-list>
<section class="participants__list">
<h2>Pågående tjänst</h2>
<dafa-participants-list
*ngIf="activeParticipants$ | async as participants; else loadingRef"
[participants]="participants"
[sortBy]="activeParticipantsSortBy$ | async"
(sorted)="handleActiveParticipantsSort($event)"
></dafa-participants-list>
</section>
<section class="participants__list">
<h2>För uppföljning</h2>
<dafa-participants-list
*ngIf="followUpParticipants$ | async as participants; else loadingRef"
[participants]="participants"
[sortBy]="followUpParticipantsSortBy$ | async"
(sorted)="handleFollowUpParticipantsSort($event)"
></dafa-participants-list>
</section>
<ng-template #loadingRef>
<digi-ng-skeleton-base [afCount]="3" afText="Laddar deltagare"></digi-ng-skeleton-base>
</ng-template>
</section>
<section class="participants__list">
<h2>För uppföljning</h2>
<dafa-participants-list
*ngIf="followUpParticipants$ | async as participants; else loadingRef"
[participants]="participants"
[sortBy]="followUpParticipantsSortBy$ | async"
(sorted)="handleFollowUpParticipantsSort($event)"
></dafa-participants-list>
</section>
<ng-template #loadingRef>
<digi-ng-skeleton-base [afCount]="3" afText="Laddar deltagare"></digi-ng-skeleton-base>
</ng-template>
</section>
</dafa-logged-in-shell>
</dafa-layout>

View File

@@ -2,7 +2,7 @@ import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { ParticipantsListModule } from './components/participants-list/participants-list.module';
import { ParticipantsRoutingModule } from './participants-routing.module';
import { ParticipantsComponent } from './participants.component';
@@ -12,11 +12,11 @@ import { ParticipantsComponent } from './participants.component';
declarations: [ParticipantsComponent],
imports: [
CommonModule,
LayoutModule,
ParticipantsRoutingModule,
FormsModule,
DigiNgSkeletonBaseModule,
ParticipantsListModule,
LoggedInShellModule,
],
})
export class ParticipantsModule {}

View File

@@ -1,12 +1,12 @@
<dafa-logged-in-shell>
<digi-typography>
<section class="releases">
<h1>Releaser</h1>
<p>
Alla förändringar i applikationen blir dokumenterade på den här sidan. Versionen som ligger högst upp är den som
är aktuell just nu.
</p>
<markdown src="assets/CHANGELOG.md"></markdown>
</section>
</digi-typography>
</dafa-logged-in-shell>
<dafa-layout>
<digi-typography>
<section class="releases">
<h1>Releaser</h1>
<p>
Alla förändringar i applikationen blir dokumenterade på den här sidan. Versionen som ligger högst upp är den som
är aktuell just nu.
</p>
<markdown src="assets/CHANGELOG.md"></markdown>
</section>
</digi-typography>
</dafa-layout>

View File

@@ -1,9 +1,9 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { MarkdownModule } from 'ngx-markdown';
import { ReleasesComponent } from './releases.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -12,7 +12,7 @@ import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-
CommonModule,
MarkdownModule.forChild(),
RouterModule.forChild([{ path: '', component: ReleasesComponent }]),
LoggedInShellModule
]
LayoutModule,
],
})
export class ReleasesModule {}

View File

@@ -1,3 +1,3 @@
<dafa-logged-in-shell>
<section class="settings">Inställningar funkar!</section>
</dafa-logged-in-shell>
<dafa-layout>
<section class="settings">Inställningar funkar!</section>
</dafa-layout>

View File

@@ -1,11 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { SettingsComponent } from './settings.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
declarations: [SettingsComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: SettingsComponent }]), LoggedInShellModule]
imports: [CommonModule, RouterModule.forChild([{ path: '', component: SettingsComponent }]), LayoutModule],
})
export class SettingsModule {}

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell>
<dafa-layout>
<section class="start">
<digi-typography>
<h1>Välkommen till Mina Sidor FA</h1>
@@ -33,4 +33,4 @@
</div>
</div>
</section>
</dafa-logged-in-shell>
</dafa-layout>

View File

@@ -1,11 +1,11 @@
import { DigiNgCardModule } from '@af/digi-ng/_card/card';
import { DigiNgLinkInternalModule } from '@af/digi-ng/_link/link-internal';
import { DigiNgNotificationAlertModule } from '@af/digi-ng/_notification/notification-alert';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { StartComponent } from './start.component';
import { DigiNgCardModule } from '@af/digi-ng/_card/card';
import { DigiNgNotificationAlertModule } from '@af/digi-ng/_notification/notification-alert';
import { DigiNgLinkInternalModule } from '@af/digi-ng/_link/link-internal';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -13,10 +13,10 @@ import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: StartComponent }]),
LayoutModule,
DigiNgCardModule,
DigiNgNotificationAlertModule,
DigiNgLinkInternalModule,
LoggedInShellModule
]
],
})
export class StartModule {}

View File

@@ -1,3 +1,3 @@
<dafa-logged-in-shell>
<dafa-layout>
<section class="statistics">Statistik funkar!</section>
</dafa-logged-in-shell>
</dafa-layout>

View File

@@ -1,11 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { StatisticsComponent } from './statistics.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
declarations: [StatisticsComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: StatisticsComponent }]), LoggedInShellModule]
imports: [CommonModule, RouterModule.forChild([{ path: '', component: StatisticsComponent }]), LayoutModule],
})
export class StatisticsModule {}

View File

@@ -1,14 +1,14 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { AuthenticationService } from './authentication.service';
import { Observable } from 'rxjs';
import { AuthenticationService } from './authentication.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthenticationService) {}
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const idToken = this.auth.getAuthorizationToken();
const idToken = this.auth.currentAuthorizationToken;
if (idToken) {
const cloned = req.clone({

View File

@@ -1,14 +1,14 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '@dafa-environment';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import {
AuthenticationApiResponse,
AuthenticationResult,
mapAuthApiResponseToAuthenticationResult,
} from '@dafa-models/authentication.model';
import { add, isBefore } from 'date-fns';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
const API_HEADERS = { headers: environment.api.headers };
@@ -16,14 +16,29 @@ const API_HEADERS = { headers: environment.api.headers };
providedIn: 'root',
})
export class AuthenticationService {
private _token$ = new BehaviorSubject<string>(null);
private _expiration$ = new BehaviorSubject<number>(null);
isLoggedIn$: Observable<boolean> = combineLatest([this._token$, this._expiration$]).pipe(
map(([token, expiration]) => {
if (token && expiration) {
return isBefore(new Date(), expiration);
}
return false;
})
);
private static _authTokenApiUrl(code: string): string {
return `${environment.api.url}/get-token?code=${code}`;
return `${environment.api.url}/auth/token?accessCode=${code}`;
}
private static _setSession(authenticationResult: AuthenticationResult): void {
private _setSession(authenticationResult: AuthenticationResult): void {
const expiresAt = add(new Date(), { seconds: authenticationResult.expiresIn });
this._token$.next(authenticationResult.idToken);
localStorage.setItem('id_token', authenticationResult.idToken);
this._expiration$.next(expiresAt.valueOf());
localStorage.setItem('expires_at', JSON.stringify(expiresAt.valueOf()));
}
@@ -31,31 +46,37 @@ export class AuthenticationService {
return this.httpClient
.get<AuthenticationApiResponse>(AuthenticationService._authTokenApiUrl(authorizationCodeFromCiam), API_HEADERS)
.pipe(
map(response => mapAuthApiResponseToAuthenticationResult(response.data)),
map(response => mapAuthApiResponseToAuthenticationResult(response)),
tap(authenticationResult => {
AuthenticationService._setSession(authenticationResult);
this._setSession(authenticationResult);
})
);
}
isLoggedIn(): boolean {
return isBefore(new Date(), this.getExpiration());
}
isLoggedOut(): boolean {
return !this.isLoggedIn();
}
getAuthorizationToken(): string {
return localStorage.getItem('id_token');
}
getExpiration(): number {
private _getLocalStorageData(): { id_token: string; expires_at: number } {
const id_token = localStorage.getItem('id_token');
const expiration = localStorage.getItem('expires_at');
return JSON.parse(expiration) as number;
return id_token && expiration
? {
id_token,
expires_at: +JSON.parse(expiration),
}
: null;
}
constructor(private httpClient: HttpClient) {}
get currentAuthorizationToken(): string {
return this._token$.getValue();
}
constructor(private httpClient: HttpClient) {
const localStorageData = this._getLocalStorageData();
if (localStorageData) {
this._token$.next(localStorageData.id_token);
this._expiration$.next(localStorageData.expires_at);
}
}
logout(): void {
localStorage.removeItem('id_token');

View File

@@ -11,8 +11,8 @@ const API_HEADERS = { headers: environment.api.headers };
providedIn: 'root',
})
export class UserService {
private _userApiUrl = `${environment.api.url}/currentUser`;
public currentUser$: Observable<User> = this.httpClient
private _userApiUrl = `${environment.api.url}/auth`;
public user$: Observable<User> = this.httpClient
.get<UserApiResponse>(this._userApiUrl, API_HEADERS)
.pipe(map(response => mapUserApiResponseToUser(response.data)));

View File

@@ -5,8 +5,8 @@
</a>
</div>
<ul class="navigation__list dafa__hide-on-print">
<li class="navigation__item navigation__item--user" *ngIf="currentUser">
<span class="navigation__text">{{ currentUser.fullName }}</span>
<li class="navigation__item navigation__item--user">
<span *ngIf="user" class="navigation__text">{{ user.fullName }}</span>
<dafa-icon [icon]="iconType.USER" size="l"></dafa-icon>
</li>
<li class="navigation__item">
@@ -21,16 +21,5 @@
<span class="dafa__a11y-sr-only">Inställningar</span>
</a>
</li>
<!-- <li class="navigation__item">
<a
class="navigation__link"
[routerLink]="['/']"
[routerLinkActive]="['navigation__link--active']"
[routerLinkActiveOptions]="{ exact: true }"
>
<dafa-icon [icon]="iconType.HOME" size="l"></dafa-icon>
<span class="navigation__text">Startsida</span>
</a>
</li> -->
</ul>
</div>

View File

@@ -9,6 +9,6 @@ import { User } from '@dafa-models/user.model';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NavigationComponent {
@Input() currentUser: User;
@Input() user: User;
iconType = IconType;
}

View File

@@ -1,8 +1,8 @@
<div class="dafa">
<div class="dafa" *ngIf="isLoggedIn$ | async">
<dafa-skip-to-content mainContentId="dafa-main-content"></dafa-skip-to-content>
<header class="dafa__header">
<dafa-navigation *ngIf="isLoggedIn" [currentUser]="currentUser$ | async"></dafa-navigation>
<dafa-navigation [user]="user$ | async"></dafa-navigation>
</header>
<dafa-sidebar class="dafa__sidebar"></dafa-sidebar>
@@ -12,10 +12,8 @@
[afItems]="breadcrumbsItems"
></digi-ng-navigation-breadcrumbs>
<ng-content></ng-content>
<ng-content></ng-content>
</main>
<dafa-footer class="dafa__footer"></dafa-footer>
</div>
<dafa-toast-list></dafa-toast-list>

View File

@@ -0,0 +1,28 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { LayoutComponent } from './Layout.component';
describe('LayoutComponent', () => {
let component: LayoutComponent;
let fixture: ComponentFixture<LayoutComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [LayoutComponent],
imports: [RouterTestingModule, HttpClientTestingModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(LayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,47 +1,54 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { BehaviorSubject, Observable } from 'rxjs';
import { User } from '@dafa-models/user.model';
import { NavigationEnd, Router } from '@angular/router';
import { UserService } from '@dafa-services/api/user.service';
import { filter } from 'rxjs/operators';
import { mapPathsToBreadcrumbs } from '@dafa-utils/map-paths-to-breadcrumbs.util';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { UnsubscribeDirective } from '@dafa-directives/unsubscribe.directive';
import { User } from '@dafa-models/user.model';
import { AuthenticationService } from '@dafa-services/api/authentication.service';
import { environment } from '@dafa-environment';
import { UserService } from '@dafa-services/api/user.service';
import { mapPathsToBreadcrumbs } from '@dafa-utils/map-paths-to-breadcrumbs.util';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
@Component({
selector: 'dafa-logged-in-shell',
templateUrl: './logged-in-shell.component.html',
styleUrls: ['./logged-in-shell.component.scss'],
selector: 'dafa-layout',
templateUrl: './layout.component.html',
styleUrls: ['./layout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LoggedInShellComponent extends UnsubscribeDirective {
export class LayoutComponent extends UnsubscribeDirective {
private startBreadcrumb: NavigationBreadcrumbsItem = {
text: 'Start',
routerLink: '/',
};
private _breadcrumbsItems$ = new BehaviorSubject<NavigationBreadcrumbsItem[]>([this.startBreadcrumb]);
public currentUser$: Observable<User> = this.userService.currentUser$;
isLoggedIn$: Observable<boolean> = this.authService.isLoggedIn$;
user$: Observable<User> = this.isLoggedIn$.pipe(
filter(loggedIn => !!loggedIn),
switchMap(() => this.userService.user$)
);
get breadcrumbsItems(): NavigationBreadcrumbsItem[] {
return this._breadcrumbsItems$.getValue();
}
get isLoggedIn(): boolean {
return this.authService.isLoggedIn();
}
constructor(private router: Router, private authService: AuthenticationService, private userService: UserService) {
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private authService: AuthenticationService,
private userService: UserService
) {
super();
if (this.authService.isLoggedOut()) {
void this.router.navigateByUrl(environment.loginUrl);
}
super.unsubscribeOnDestroy(
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
const urlTree = this.router.parseUrl(this.router.url);
if (urlTree.queryParams.code) {
void this.router.navigate([], {
relativeTo: this.activatedRoute,
queryParams: { code: null },
queryParamsHandling: 'merge',
replaceUrl: true,
});
}
urlTree.queryParams = {};
const paths = urlTree
.toString()

View File

@@ -0,0 +1,27 @@
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MarkdownModule } from 'ngx-markdown';
import { FooterModule } from './components/footer/footer.module';
import { NavigationModule } from './components/navigation/navigation.module';
import { SidebarModule } from './components/sidebar/sidebar.module';
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
import { LayoutComponent } from './layout.component';
@NgModule({
declarations: [LayoutComponent],
imports: [
CommonModule,
RouterModule,
SkipToContentModule,
NavigationModule,
SidebarModule,
FooterModule,
MarkdownModule.forRoot({ loader: HttpClient }),
DigiNgNavigationBreadcrumbsModule,
],
exports: [LayoutComponent],
})
export class LayoutModule {}

View File

@@ -0,0 +1,11 @@
export const environment = {
environment: 'api',
clientId: '5d08c2e4-763e-42f6-b858-24e4773bb83d',
redirectUri: 'http://localhost:4200',
loginUrl: 'https://ciam-test.arbetsformedlingen.se:8443/uas/oauth2/authorization?response_type=code&scope=openid',
production: false,
api: {
url: '/api',
headers: {},
},
};

View File

@@ -1,10 +0,0 @@
export const environment = {
loginUrl:
// eslint-disable-next-line max-len
'https://ciam-test.arbetsformedlingen.se:8443/uas/oauth2/authorization?response_type=code&scope=openid&redirect_uri=https://localhost:4200/ciam-landing&client_id=5d08c2e4-763e-42f6-b858-24e4773bb83d',
production: false,
api: {
url: '/api',
headers: {},
},
};

View File

@@ -1,11 +0,0 @@
export const environment = {
production: false,
api: {
meet: 'https://dafa-utv.tocp.arbetsformedlingen.se/prweb/api/meettest/v1',
default: 'https://dafa-utv.tocp.arbetsformedlingen.se/prweb/api/v1/data',
url: '/api',
headers: {
Authorization: 'Basic dGVzdHVzZXIxOmRhZmFAMTIz', // user: testuser1, password: dafa@123
},
},
};

View File

@@ -1,5 +1,8 @@
export const environment = {
loginUrl: 'mock-login',
environment: 'local',
clientId: '',
redirectUri: '',
loginUrl: '/mock-login',
production: false,
api: {
url: '/api',

View File

@@ -0,0 +1,7 @@
{
"/api": {
"target": "http://mina-sidor-fa-test-api.tocp.arbetsformedlingen.se",
"secure": false,
"changeOrigin": true
}
}

View File

@@ -1,6 +0,0 @@
{
"/api": {
"target": "http://localhost:6001",
"secure": false
}
}

View File

@@ -15,17 +15,18 @@ server.use(
'/employee*': '/employees$1',
'/participants': '/participants?_embed=employees',
'/participant/:id': '/participants/:id?_embed=employees',
'/auth': '/currentUser',
'*page=*': '$1_page=$2',
'*limit=*': '$1_limit=$2',
'*sort=*': '$1_sort=$2',
'*order=*': '$1_order=$2',
'/get-token?code=auth_code_from_CIAM_with_all_permissions': '/getTokenFullAccess',
'/auth/token?accessCode=auth_code_from_CIAM_with_all_permissions': '/getTokenFullAccess',
})
);
router.render = (req, res) => {
// all paths except getToken requires Authorization header.
if (!req._parsedUrl.pathname.includes('getToken') && !req.headers.authorization) {
// all paths except getTokenFullAccess requires Authorization header.
if (!req._parsedUrl.pathname.includes('getTokenFullAccess') && !req.headers.authorization) {
return res.status(401).jsonp({ error: 'No valid access-token' });
}
@@ -36,10 +37,14 @@ router.render = (req, res) => {
req.body.createdAt = Date.now();
}
res.jsonp({
data: res.locals.data,
...appendMetaData(params, res),
});
if (req._parsedUrl.pathname.includes('getTokenFullAccess')) {
res.jsonp(res.locals.data);
} else {
res.jsonp({
data: res.locals.data,
...appendMetaData(params, res),
});
}
};
server.use(router);

View File

@@ -18,6 +18,7 @@
"@dafa-assets/*": ["apps/dafa-web/src/assets/*"],
"@dafa-shared/*": ["apps/dafa-web/src/app/shared/*"],
"@dafa-models/*": ["apps/dafa-web/src/app/data/models/*"],
"@dafa-guards/*": ["apps/dafa-web/src/app/guards/*"],
"@dafa-constants/*": ["apps/dafa-web/src/app/data/constants/*"],
"@dafa-directives/*": ["apps/dafa-web/src/app/directives/*"],
"@dafa-enums/*": ["apps/dafa-web/src/app/data/enums/*"],