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" "browserTarget": "dafa-web:build:production"
}, },
"api": { "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 { NgModule } from '@angular/core';
import { ExtraOptions, RouterModule, Routes } from '@angular/router'; import { ExtraOptions, RouterModule, Routes } from '@angular/router';
import { environment } from '@dafa-environment'; import { environment } from '@dafa-environment';
import { AuthGuard } from '@dafa-guards/auth.guard';
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
data: { title: '' }, data: { title: '' },
loadChildren: () => import('./pages/start/start.module').then(m => m.StartModule), loadChildren: () => import('./pages/start/start.module').then(m => m.StartModule),
canActivate: [AuthGuard],
}, },
{ {
path: 'administration', path: 'administration',
data: { title: 'Administration' }, data: { title: 'Administration' },
loadChildren: () => import('./pages/administration/administration.module').then(m => m.AdministrationModule), loadChildren: () => import('./pages/administration/administration.module').then(m => m.AdministrationModule),
canActivate: [AuthGuard],
}, },
{ {
path: 'deltagare', path: 'deltagare',
data: { title: 'Deltagare' }, data: { title: 'Deltagare' },
loadChildren: () => import('./pages/participants/participants.module').then(m => m.ParticipantsModule), loadChildren: () => import('./pages/participants/participants.module').then(m => m.ParticipantsModule),
canActivate: [AuthGuard],
}, },
{ {
path: 'avrop', path: 'avrop',
data: { title: 'Avrop' }, data: { title: 'Avrop' },
loadChildren: () => import('./pages/avrop/avrop.module').then(m => m.AvropModule), loadChildren: () => import('./pages/avrop/avrop.module').then(m => m.AvropModule),
canActivate: [AuthGuard],
}, },
{ {
path: 'meddelanden', path: 'meddelanden',
data: { title: 'Meddelanden' }, data: { title: 'Meddelanden' },
loadChildren: () => import('./pages/messages/messages.module').then(m => m.MessagesModule), loadChildren: () => import('./pages/messages/messages.module').then(m => m.MessagesModule),
canActivate: [AuthGuard],
}, },
{ {
path: 'statistik', path: 'statistik',
data: { title: 'Statistik' }, data: { title: 'Statistik' },
loadChildren: () => import('./pages/statistics/statistics.module').then(m => m.StatisticsModule), loadChildren: () => import('./pages/statistics/statistics.module').then(m => m.StatisticsModule),
canActivate: [AuthGuard],
}, },
{ {
path: 'installningar', path: 'installningar',
data: { title: 'Inställningar' }, data: { title: 'Inställningar' },
loadChildren: () => import('./pages/settings/settings.module').then(m => m.SettingsModule), loadChildren: () => import('./pages/settings/settings.module').then(m => m.SettingsModule),
canActivate: [AuthGuard],
}, },
{ {
path: 'releases', path: 'releases',
data: { title: 'Releases' }, data: { title: 'Releases' },
loadChildren: () => import('./pages/releases/releases.module').then(m => m.ReleasesModule), loadChildren: () => import('./pages/releases/releases.module').then(m => m.ReleasesModule),
}, canActivate: [AuthGuard],
{
path: 'ciam-landing',
data: { title: 'Ciam landing page' },
loadChildren: () => import('./pages/ciam-landing/ciam-landing.module').then(m => m.CiamLandingModule),
}, },
{ {
path: 'logout', path: 'logout',
data: { title: 'Ciam landing page' }, data: { title: 'Logga ut' },
loadChildren: () => import('./pages/logout/logout.module').then(m => m.LogoutModule), loadChildren: () => import('./pages/logout/logout.module').then(m => m.LogoutModule),
}, },
]; ];
@@ -67,6 +71,7 @@ routes.push({
path: '**', path: '**',
data: { title: 'Sidan hittas inte' }, data: { title: 'Sidan hittas inte' },
loadChildren: () => import('./pages/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule), loadChildren: () => import('./pages/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule),
canActivate: [AuthGuard],
}); });
const options: ExtraOptions = { 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 { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component'; 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', () => { describe('AppComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AppComponent], declarations: [AppComponent],
imports: [RouterTestingModule, HttpClientTestingModule, SkipToContentModule, NavigationModule, SidebarModule], imports: [RouterTestingModule, HttpClientTestingModule],
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -6,6 +6,4 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
styleUrls: ['./app.component.scss'], styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, 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 { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
import { ErrorHandler, NgModule } from '@angular/core'; import { ErrorHandler, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; 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 { 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 { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; 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 { ToastListModule } from './components/toast-list/toast-list.module';
import { AuthInterceptor } from '@dafa-services/api/auth.interceptor';
import { AvropModule } from './pages/avrop/avrop.module'; import { AvropModule } from './pages/avrop/avrop.module';
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
imports: [ imports: [BrowserModule, HttpClientModule, AppRoutingModule, ToastListModule, AvropModule],
BrowserModule,
HttpClientModule,
AppRoutingModule,
RouterModule,
SkipToContentModule,
NavigationModule,
SidebarModule,
ToastListModule,
FooterModule,
MarkdownModule.forRoot({ loader: HttpClient }),
DigiNgNavigationBreadcrumbsModule,
AvropModule,
],
providers: [ providers: [
{ {
provide: ErrorHandler, provide: ErrorHandler,
useClass: CustomErrorHandler, useClass: CustomErrorHandler,
}, },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
AuthGuard,
], ],
bootstrap: [AppComponent], 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 { export interface AuthenticationResult {
idToken: string; idToken: string;
expiresIn: number; expiresIn: number;
} }
export interface AuthenticationApiResponse { export interface AuthenticationApiResponse {
data: AuthenticationApiResponseData;
}
export interface AuthenticationApiResponseData {
id: string; id: string;
access_token: string; access_token: string;
scope: string; scope: string;
@@ -18,12 +12,10 @@ export interface AuthenticationApiResponseData {
expires_in: number; expires_in: number;
} }
export function mapAuthApiResponseToAuthenticationResult(data: AuthenticationApiResponseData): AuthenticationResult { export function mapAuthApiResponseToAuthenticationResult(data: AuthenticationApiResponse): AuthenticationResult {
const { const { id_token, expires_in } = data;
id_token,
expires_in } = data;
return { return {
idToken: id_token, 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"> <section class="employee-card">
<digi-typography *ngIf="detailedEmployeeData$ | async as detailedEmployeeData; else loadingRef"> <digi-typography *ngIf="detailedEmployeeData$ | async as detailedEmployeeData; else loadingRef">
<div class="employee-card__editcontainer"> <div class="employee-card__editcontainer">
@@ -90,4 +90,4 @@
<span class="dafa__a11y-sr-only">Info saknas</span> <span class="dafa__a11y-sr-only">Info saknas</span>
</dd> </dd>
</ng-template> </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 { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; 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 { LocalDatePipeModule } from '@dafa-shared/pipes/local-date/local-date.module';
import { EmployeeCardComponent } from './employee-card.component'; 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({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -14,11 +14,11 @@ import { HideTextModule } from '@dafa-shared/components/hide-text/hide-text.modu
imports: [ imports: [
CommonModule, CommonModule,
RouterModule.forChild([{ path: '', component: EmployeeCardComponent }]), RouterModule.forChild([{ path: '', component: EmployeeCardComponent }]),
LayoutModule,
DigiNgSkeletonBaseModule, DigiNgSkeletonBaseModule,
DigiNgLayoutExpansionPanelModule, DigiNgLayoutExpansionPanelModule,
LocalDatePipeModule, LocalDatePipeModule,
HideTextModule, HideTextModule,
LoggedInShellModule
], ],
}) })
export class EmployeeCardModule {} export class EmployeeCardModule {}

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell> <dafa-layout>
<section class="employee-form"> <section class="employee-form">
<digi-typography> <digi-typography>
<h1>Skapa nytt konto</h1> <h1>Skapa nytt konto</h1>
@@ -87,8 +87,10 @@
</digi-typography> </digi-typography>
<ul class="employee-form__authorizations"> <ul class="employee-form__authorizations">
<li *ngFor="let authorization of authorizations; let first = first" <li
class="employee-form__authorization-item"> *ngFor="let authorization of authorizations; let first = first"
class="employee-form__authorization-item"
>
<digi-form-checkbox <digi-form-checkbox
class="employee-form__digi-checkbox" class="employee-form__digi-checkbox"
[afId]="(first && 'employee-form-authorizations') || undefined" [afId]="(first && 'employee-form-authorizations') || undefined"
@@ -99,9 +101,13 @@
[afChecked]="authorizationsControl.value.includes(authorization)" [afChecked]="authorizationsControl.value.includes(authorization)"
(afOnChange)="toggleAuthorization(authorization, $event.detail.target.checked)" (afOnChange)="toggleAuthorization(authorization, $event.detail.target.checked)"
></digi-form-checkbox> ></digi-form-checkbox>
<digi-button af-variation="secondary" [afAriaLabel]="'Läs mer om ' + authorization.name" <digi-button
af-size="s" class="employee-form__read-more" af-variation="secondary"
(afOnClick)="openDialog(true, authorization.name)"> [afAriaLabel]="'Läs mer om ' + authorization.name"
af-size="s"
class="employee-form__read-more"
(afOnClick)="openDialog(true, authorization.name)"
>
Läs mer Läs mer
</digi-button> </digi-button>
</li> </li>
@@ -117,7 +123,9 @@
</div> </div>
<div class="employee-form__footer"> <div class="employee-form__footer">
<digi-button af-type="reset" af-variation="secondary" (afOnClick)="resetForm($event.detail)">Avbryt</digi-button> <digi-button af-type="reset" af-variation="secondary" (afOnClick)="resetForm($event.detail)"
>Avbryt</digi-button
>
<digi-button af-type="submit">Registrera konto</digi-button> <digi-button af-type="submit">Registrera konto</digi-button>
</div> </div>
@@ -128,17 +136,15 @@
(afOnPrimaryClick)="openDialog(false)" (afOnPrimaryClick)="openDialog(false)"
[afHeading]="modalAuthInfo.name" [afHeading]="modalAuthInfo.name"
afHeadingLevel="h3" afHeadingLevel="h3"
afPrimaryButtonText="Stäng"> afPrimaryButtonText="Stäng"
>
<p> <p>
Behörigheten passar personer som arbetar nära deltagare. Behörigheten passar personer som arbetar nära deltagare. Behörigheten kan användas av exempelvis handledare,
Behörigheten kan användas av exempelvis handledare, coacher, studie- och yrkesvägledare, coacher, studie- och yrkesvägledare, lärare eller annan roll som behöver kunna se information om deltager,
lärare eller annan roll som behöver kunna se information om deltager, kontakta deltagare, kontakta deltagare, planera aktiviteter med deltagre och hantera rapporter för deltagre.
planera aktiviteter med deltagre och hantera rapporter för deltagre.
</p> </p>
<p> <p>Behörigheten ger tillgång till och utföra aktiviteter i följande funktioner i systemet:</p>
Behörigheten ger tillgång till och utföra aktiviteter i följande funktioner i systemet:
</p>
<p> <p>
- Deltagarlista <br /> - Deltagarlista <br />
@@ -155,4 +161,4 @@
</digi-ng-dialog> </digi-ng-dialog>
</form> </form>
</section> </section>
</dafa-logged-in-shell> </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 { DigiNgFormCheckboxModule } from '@af/digi-ng/_form/form-checkbox';
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker'; import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
import { DigiNgFormInputModule } from '@af/digi-ng/_form/form-input'; 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 { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; 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 { LocalDatePipeModule } from '@dafa-shared/pipes/local-date/local-date.module';
import { EmployeeFormComponent } from './employee-form.component'; 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({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -19,6 +19,7 @@ import { LoggedInShellModule } from '../../../../components/logged-in-shell/logg
imports: [ imports: [
CommonModule, CommonModule,
RouterModule.forChild([{ path: '', component: EmployeeFormComponent }]), RouterModule.forChild([{ path: '', component: EmployeeFormComponent }]),
LayoutModule,
ReactiveFormsModule, ReactiveFormsModule,
LocalDatePipeModule, LocalDatePipeModule,
DigiNgFormInputModule, DigiNgFormInputModule,
@@ -28,7 +29,6 @@ import { LoggedInShellModule } from '../../../../components/logged-in-shell/logg
DigiNgPopoverModule, DigiNgPopoverModule,
DigiNgFormCheckboxModule, DigiNgFormCheckboxModule,
DigiNgDialogModule, DigiNgDialogModule,
LoggedInShellModule ],
]
}) })
export class EmployeeFormModule {} export class EmployeeFormModule {}

View File

@@ -4,9 +4,13 @@
<thead> <thead>
<tr> <tr>
<th scope="col" class="employees-list__column-head" *ngFor="let column of columnHeaders"> <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}} {{column.label}}
<ng-container *ngIf="sort.key === column"> <ng-container *ngIf="sort.key === column.key">
<digi-icon-caret-up <digi-icon-caret-up
class="employees-list__sort-icon" class="employees-list__sort-icon"
*ngIf="sort.order === orderType.ASC" *ngIf="sort.order === orderType.ASC"

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell> <dafa-layout>
<section class="employees"> <section class="employees">
<digi-typography> <digi-typography>
<h1>Personal</h1> <h1>Personal</h1>
@@ -37,4 +37,4 @@
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base> <digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base>
</ng-template> </ng-template>
</section> </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 { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { EmployeesListModule } from './components/employees-list/employees-list.module'; import { EmployeesListModule } from './components/employees-list/employees-list.module';
import { EmployeesComponent } from './employees.component'; import { EmployeesComponent } from './employees.component';
import { LoggedInShellModule } from '../../../../components/logged-in-shell/logged-in-shell.module';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -15,12 +15,12 @@ import { LoggedInShellModule } from '../../../../components/logged-in-shell/logg
imports: [ imports: [
CommonModule, CommonModule,
RouterModule.forChild([{ path: '', component: EmployeesComponent }]), RouterModule.forChild([{ path: '', component: EmployeesComponent }]),
LayoutModule,
DigiNgLinkInternalModule, DigiNgLinkInternalModule,
DigiNgSkeletonBaseModule, DigiNgSkeletonBaseModule,
EmployeesListModule, EmployeesListModule,
DigiNgLinkButtonModule, DigiNgLinkButtonModule,
FormsModule, FormsModule,
LoggedInShellModule ],
]
}) })
export class EmployeesModule {} export class EmployeesModule {}

View File

@@ -1,22 +1,16 @@
<dafa-logged-in-shell> <dafa-layout>
<section class="call-off" *ngIf="currentStep$ | async; let currentStep; else loadingRef"> <section class="call-off" *ngIf="currentStep$ | async; let currentStep; else: loadingRef">
<digi-typography> <digi-typography>
<h2>Välj deltagare att tilldela</h2> <h2>Välj deltagare att tilldela</h2>
<p>Steg {{ currentStep }} av {{ steps }}:</p> <p>Steg {{ currentStep }} av {{ steps }}:</p>
</digi-typography> </digi-typography>
<digi-ng-progress-progressbar <digi-ng-progress-progressbar [afSteps]="steps" afAriaLabel="An aria label" [afActiveStep]="currentStep">
[afSteps]="steps"
afAriaLabel="An aria label"
[afActiveStep]="currentStep">
</digi-ng-progress-progressbar> </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"> <ng-container *ngIf="currentStep == 4">
<h2>Avropet är sparat</h2> <h2>Avropet är sparat</h2>
<digi-button <digi-button af-size="m" class="employee-form__read-more" (afOnClick)="goToStep1()">
af-size="m"
class="employee-form__read-more"
(afOnClick)="goToStep1()">
Tillbaka till nya deltagare Tillbaka till nya deltagare
</digi-button> </digi-button>
</ng-container> </ng-container>
@@ -39,64 +33,58 @@
</ng-container> </ng-container>
<ng-container *ngIf="currentStep == 1"> <ng-container *ngIf="currentStep == 1">
<digi-button <digi-button af-size="m" class="employee-form__read-more" (afOnClick)="lockSelectedDeltagare()">
af-size="m"
class="employee-form__read-more"
(afOnClick)="lockSelectedDeltagare()">
Lås deltagare Lås deltagare
</digi-button> </digi-button>
</ng-container> </ng-container>
<ng-container *ngIf="currentStep == 2"> <ng-container *ngIf="currentStep == 2">
<h2>Välj handledare</h2> <h2>Välj handledare</h2>
<ng-container *ngIf="selectableHandledareList$ | async; let selectableHandledareList; else loadingRefSmall"> <ng-container *ngIf="selectableHandledareList$ | async; let selectableHandledareList; else: loadingRefSmall">
<select [value]="(selectedHandledare$ | async)?.id ? (selectedHandledare$ | async)?.id : ''" (change)="changeHandledare($event)"> <select
[value]="(selectedHandledare$ | async)?.id ? (selectedHandledare$ | async)?.id : ''"
(change)="changeHandledare($event)"
>
<option disabled value="">Välj handledare</option> <option disabled value="">Välj handledare</option>
<option <option *ngFor="let selectableHandledare of selectableHandledareList" [value]="selectableHandledare?.id">
*ngFor="let selectableHandledare of selectableHandledareList" {{ selectableHandledare?.fullName }}
[value]="selectableHandledare?.id" </option>
>{{selectableHandledare?.fullName}}</option>
</select> </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> </ng-container>
<br /><br />
<br><br> <digi-button
<digi-button af-variation="secondary" af-variation="secondary"
af-size="m" af-size="m"
class="employee-form__read-more" class="employee-form__read-more"
(afOnClick)="unlockSelectedDeltagare()"> (afOnClick)="unlockSelectedDeltagare()"
>
Tillbaka Tillbaka
</digi-button> </digi-button>
<digi-button <digi-button af-size="m" class="employee-form__read-more" (afOnClick)="confirmHandledare()">
af-size="m"
class="employee-form__read-more"
(afOnClick)="confirmHandledare()">
Tilldela Tilldela
</digi-button> </digi-button>
</ng-container> </ng-container>
<div *ngIf="currentStep == 3"> <div *ngIf="currentStep == 3">
<br><br> <br /><br />
<digi-button af-variation="secondary" <digi-button
af-variation="secondary"
af-size="m" af-size="m"
class="employee-form__read-more" class="employee-form__read-more"
(afOnClick)="unconfirmHandledare()"> (afOnClick)="unconfirmHandledare()"
>
Tillbaka Tillbaka
</digi-button> </digi-button>
<digi-button <digi-button af-size="m" class="employee-form__read-more" (afOnClick)="save()"> Spara avrop </digi-button>
af-size="m"
class="employee-form__read-more"
(afOnClick)="save()">
Spara avrop
</digi-button>
</div> </div>
</div> </div>
</section> </section>
</dafa-logged-in-shell>
<ng-template #loadingRef> <ng-template #loadingRef>
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base> <digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base>
@@ -106,5 +94,5 @@
<digi-icon-spinner af-title="Laddar innehåll"></digi-icon-spinner> <digi-icon-spinner af-title="Laddar innehåll"></digi-icon-spinner>
</ng-template> </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 { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { AvropComponent } from './avrop.component'; import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
import { DigiNgProgressProgressbarModule } from '@af/digi-ng/_progress/progressbar';
import { AvropFiltersComponent } from './avrop-filters/avrop-filters.component'; 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 { 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({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AvropComponent, AvropFiltersComponent, AvropTableComponent, AvropTableRowComponent, TemporaryFilterComponent], declarations: [
AvropComponent,
AvropFiltersComponent,
AvropTableComponent,
AvropTableRowComponent,
TemporaryFilterComponent,
],
imports: [ imports: [
CommonModule, CommonModule,
RouterModule.forChild([{ path: '', component: AvropComponent }]), RouterModule.forChild([{ path: '', component: AvropComponent }]),
LoggedInShellModule, LayoutModule,
DigiNgProgressProgressbarModule, DigiNgProgressProgressbarModule,
DigiNgSkeletonBaseModule, 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 { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { LogoutComponent } from './logout.component'; import { LogoutComponent } from './logout.component';
import { DigiNgButtonModule } from '@af/digi-ng/_button/button';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],

View File

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

View File

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

View File

@@ -1,8 +1,11 @@
<digi-typography> <digi-typography>
<section class="login__wrapper"> <section class="mock-login">
<h1>Mock login</h1> <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> <p>
<digi-ng-button [routerLink]="'/ciam-landing'" [queryParams]="{code: 'auth_code_from_CIAM_with_all_permissions'}"> 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 Logga in med fullständiga rättigheter
</digi-ng-button> </digi-ng-button>
</section> </section>

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell> <dafa-layout>
<digi-typography> <digi-typography>
<section class="page-not-found"> <section class="page-not-found">
<h1>Oj då! Vi kan inte hitta sidan.</h1> <h1>Oj då! Vi kan inte hitta sidan.</h1>
@@ -9,4 +9,4 @@
</a> </a>
</section> </section>
</digi-typography> </digi-typography>
</dafa-logged-in-shell> </dafa-layout>

View File

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

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell> <dafa-layout>
<section class="participant-card"> <section class="participant-card">
<digi-typography *ngIf="detailedParticipantData$ | async as detailedParticipantData; else loadingRef"> <digi-typography *ngIf="detailedParticipantData$ | async as detailedParticipantData; else loadingRef">
<header class="participant-card__header"> <header class="participant-card__header">
@@ -54,4 +54,4 @@
<span class="dafa__a11y-sr-only">Info saknas</span> <span class="dafa__a11y-sr-only">Info saknas</span>
</dd> </dd>
</ng-template> </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 { RouterModule } from '@angular/router';
import { BackLinkModule } from '@dafa-shared/components/back-link/back-link.module'; import { BackLinkModule } from '@dafa-shared/components/back-link/back-link.module';
import { IconModule } from '@dafa-shared/components/icon/icon.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 { ParticipantCardComponent } from './participant-card.component';
import { LoggedInShellModule } from '../../../../components/logged-in-shell/logged-in-shell.module';
@NgModule({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -13,7 +13,7 @@ import { LoggedInShellModule } from '../../../../components/logged-in-shell/logg
imports: [ imports: [
CommonModule, CommonModule,
RouterModule.forChild([{ path: '', component: ParticipantCardComponent }]), RouterModule.forChild([{ path: '', component: ParticipantCardComponent }]),
LoggedInShellModule, LayoutModule,
DigiNgLinkInternalModule, DigiNgLinkInternalModule,
IconModule, IconModule,
BackLinkModule, BackLinkModule,

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell> <dafa-layout>
<section class="participants"> <section class="participants">
<digi-typography> <digi-typography>
<h1>Mina deltagare</h1> <h1>Mina deltagare</h1>
@@ -41,4 +41,4 @@
<digi-ng-skeleton-base [afCount]="3" afText="Laddar deltagare"></digi-ng-skeleton-base> <digi-ng-skeleton-base [afCount]="3" afText="Laddar deltagare"></digi-ng-skeleton-base>
</ng-template> </ng-template>
</section> </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 { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; 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 { ParticipantsListModule } from './components/participants-list/participants-list.module';
import { ParticipantsRoutingModule } from './participants-routing.module'; import { ParticipantsRoutingModule } from './participants-routing.module';
import { ParticipantsComponent } from './participants.component'; import { ParticipantsComponent } from './participants.component';
@@ -12,11 +12,11 @@ import { ParticipantsComponent } from './participants.component';
declarations: [ParticipantsComponent], declarations: [ParticipantsComponent],
imports: [ imports: [
CommonModule, CommonModule,
LayoutModule,
ParticipantsRoutingModule, ParticipantsRoutingModule,
FormsModule, FormsModule,
DigiNgSkeletonBaseModule, DigiNgSkeletonBaseModule,
ParticipantsListModule, ParticipantsListModule,
LoggedInShellModule,
], ],
}) })
export class ParticipantsModule {} export class ParticipantsModule {}

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell> <dafa-layout>
<digi-typography> <digi-typography>
<section class="releases"> <section class="releases">
<h1>Releaser</h1> <h1>Releaser</h1>
@@ -9,4 +9,4 @@
<markdown src="assets/CHANGELOG.md"></markdown> <markdown src="assets/CHANGELOG.md"></markdown>
</section> </section>
</digi-typography> </digi-typography>
</dafa-logged-in-shell> </dafa-layout>

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
<dafa-logged-in-shell> <dafa-layout>
<section class="start"> <section class="start">
<digi-typography> <digi-typography>
<h1>Välkommen till Mina Sidor FA</h1> <h1>Välkommen till Mina Sidor FA</h1>
@@ -33,4 +33,4 @@
</div> </div>
</div> </div>
</section> </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 { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { LayoutModule } from '@dafa-shared/components/layout/layout.module';
import { StartComponent } from './start.component'; 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({ @NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -13,10 +13,10 @@ import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-
imports: [ imports: [
CommonModule, CommonModule,
RouterModule.forChild([{ path: '', component: StartComponent }]), RouterModule.forChild([{ path: '', component: StartComponent }]),
LayoutModule,
DigiNgCardModule, DigiNgCardModule,
DigiNgNotificationAlertModule, DigiNgNotificationAlertModule,
DigiNgLinkInternalModule, DigiNgLinkInternalModule,
LoggedInShellModule ],
]
}) })
export class StartModule {} export class StartModule {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,8 +5,8 @@
</a> </a>
</div> </div>
<ul class="navigation__list dafa__hide-on-print"> <ul class="navigation__list dafa__hide-on-print">
<li class="navigation__item navigation__item--user" *ngIf="currentUser"> <li class="navigation__item navigation__item--user">
<span class="navigation__text">{{ currentUser.fullName }}</span> <span *ngIf="user" class="navigation__text">{{ user.fullName }}</span>
<dafa-icon [icon]="iconType.USER" size="l"></dafa-icon> <dafa-icon [icon]="iconType.USER" size="l"></dafa-icon>
</li> </li>
<li class="navigation__item"> <li class="navigation__item">
@@ -21,16 +21,5 @@
<span class="dafa__a11y-sr-only">Inställningar</span> <span class="dafa__a11y-sr-only">Inställningar</span>
</a> </a>
</li> </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> </ul>
</div> </div>

View File

@@ -9,6 +9,6 @@ import { User } from '@dafa-models/user.model';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class NavigationComponent { export class NavigationComponent {
@Input() currentUser: User; @Input() user: User;
iconType = IconType; 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> <dafa-skip-to-content mainContentId="dafa-main-content"></dafa-skip-to-content>
<header class="dafa__header"> <header class="dafa__header">
<dafa-navigation *ngIf="isLoggedIn" [currentUser]="currentUser$ | async"></dafa-navigation> <dafa-navigation [user]="user$ | async"></dafa-navigation>
</header> </header>
<dafa-sidebar class="dafa__sidebar"></dafa-sidebar> <dafa-sidebar class="dafa__sidebar"></dafa-sidebar>
@@ -13,9 +13,7 @@
></digi-ng-navigation-breadcrumbs> ></digi-ng-navigation-breadcrumbs>
<ng-content></ng-content> <ng-content></ng-content>
</main> </main>
<dafa-footer class="dafa__footer"></dafa-footer> <dafa-footer class="dafa__footer"></dafa-footer>
</div> </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 { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { BehaviorSubject, Observable } from 'rxjs'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { User } from '@dafa-models/user.model'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
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 { UnsubscribeDirective } from '@dafa-directives/unsubscribe.directive'; import { UnsubscribeDirective } from '@dafa-directives/unsubscribe.directive';
import { User } from '@dafa-models/user.model';
import { AuthenticationService } from '@dafa-services/api/authentication.service'; 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({ @Component({
selector: 'dafa-logged-in-shell', selector: 'dafa-layout',
templateUrl: './logged-in-shell.component.html', templateUrl: './layout.component.html',
styleUrls: ['./logged-in-shell.component.scss'], styleUrls: ['./layout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class LoggedInShellComponent extends UnsubscribeDirective { export class LayoutComponent extends UnsubscribeDirective {
private startBreadcrumb: NavigationBreadcrumbsItem = { private startBreadcrumb: NavigationBreadcrumbsItem = {
text: 'Start', text: 'Start',
routerLink: '/', routerLink: '/',
}; };
private _breadcrumbsItems$ = new BehaviorSubject<NavigationBreadcrumbsItem[]>([this.startBreadcrumb]); 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[] { get breadcrumbsItems(): NavigationBreadcrumbsItem[] {
return this._breadcrumbsItems$.getValue(); return this._breadcrumbsItems$.getValue();
} }
get isLoggedIn(): boolean { constructor(
return this.authService.isLoggedIn(); private router: Router,
} private activatedRoute: ActivatedRoute,
private authService: AuthenticationService,
constructor(private router: Router, private authService: AuthenticationService, private userService: UserService) { private userService: UserService
) {
super(); super();
if (this.authService.isLoggedOut()) {
void this.router.navigateByUrl(environment.loginUrl);
}
super.unsubscribeOnDestroy( super.unsubscribeOnDestroy(
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => { this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
const urlTree = this.router.parseUrl(this.router.url); 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 = {}; urlTree.queryParams = {};
const paths = urlTree const paths = urlTree
.toString() .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 = { export const environment = {
loginUrl: 'mock-login', environment: 'local',
clientId: '',
redirectUri: '',
loginUrl: '/mock-login',
production: false, production: false,
api: { api: {
url: '/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', '/employee*': '/employees$1',
'/participants': '/participants?_embed=employees', '/participants': '/participants?_embed=employees',
'/participant/:id': '/participants/:id?_embed=employees', '/participant/:id': '/participants/:id?_embed=employees',
'/auth': '/currentUser',
'*page=*': '$1_page=$2', '*page=*': '$1_page=$2',
'*limit=*': '$1_limit=$2', '*limit=*': '$1_limit=$2',
'*sort=*': '$1_sort=$2', '*sort=*': '$1_sort=$2',
'*order=*': '$1_order=$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) => { router.render = (req, res) => {
// all paths except getToken requires Authorization header. // all paths except getTokenFullAccess requires Authorization header.
if (!req._parsedUrl.pathname.includes('getToken') && !req.headers.authorization) { if (!req._parsedUrl.pathname.includes('getTokenFullAccess') && !req.headers.authorization) {
return res.status(401).jsonp({ error: 'No valid access-token' }); return res.status(401).jsonp({ error: 'No valid access-token' });
} }
@@ -36,10 +37,14 @@ router.render = (req, res) => {
req.body.createdAt = Date.now(); req.body.createdAt = Date.now();
} }
if (req._parsedUrl.pathname.includes('getTokenFullAccess')) {
res.jsonp(res.locals.data);
} else {
res.jsonp({ res.jsonp({
data: res.locals.data, data: res.locals.data,
...appendMetaData(params, res), ...appendMetaData(params, res),
}); });
}
}; };
server.use(router); server.use(router);

View File

@@ -18,6 +18,7 @@
"@dafa-assets/*": ["apps/dafa-web/src/assets/*"], "@dafa-assets/*": ["apps/dafa-web/src/assets/*"],
"@dafa-shared/*": ["apps/dafa-web/src/app/shared/*"], "@dafa-shared/*": ["apps/dafa-web/src/app/shared/*"],
"@dafa-models/*": ["apps/dafa-web/src/app/data/models/*"], "@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-constants/*": ["apps/dafa-web/src/app/data/constants/*"],
"@dafa-directives/*": ["apps/dafa-web/src/app/directives/*"], "@dafa-directives/*": ["apps/dafa-web/src/app/directives/*"],
"@dafa-enums/*": ["apps/dafa-web/src/app/data/enums/*"], "@dafa-enums/*": ["apps/dafa-web/src/app/data/enums/*"],