Resubida app de ejemplo, la anterior no logueaba.

This commit is contained in:
Eneko Nieto
2021-01-20 10:38:04 +01:00
parent 88949dbadf
commit a70f652221
35 changed files with 1762 additions and 1163 deletions

View File

@@ -14,4 +14,5 @@ last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

7
.directory Normal file
View File

@@ -0,0 +1,7 @@
[Dolphin]
Timestamp=2021,1,20,10,33,51
Version=4
ViewMode=1
[Settings]
HiddenFilesShown=true

View File

@@ -1,6 +1,6 @@
# OkupamicocheAngular
# SampleOauth
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 11.0.7.
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.1.
## Development server

View File

@@ -3,16 +3,9 @@
"version": 1,
"newProjectRoot": "projects",
"projects": {
"okupamicoche-angular": {
"sample-oauth": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
},
"@schematics/angular:application": {
"strict": true
}
},
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
@@ -20,7 +13,7 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/okupamicoche-angular",
"outputPath": "dist/sample-oauth",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
@@ -31,7 +24,7 @@
"src/assets"
],
"styles": [
"src/styles.scss"
"src/styles.css"
],
"scripts": []
},
@@ -46,6 +39,7 @@
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
@@ -53,13 +47,13 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
@@ -68,18 +62,18 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "okupamicoche-angular:build"
"browserTarget": "sample-oauth:build"
},
"configurations": {
"production": {
"browserTarget": "okupamicoche-angular:build:production"
"browserTarget": "sample-oauth:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "okupamicoche-angular:build"
"browserTarget": "sample-oauth:build"
}
},
"test": {
@@ -94,7 +88,7 @@
"src/assets"
],
"styles": [
"src/styles.scss"
"src/styles.css"
],
"scripts": []
}
@@ -116,16 +110,15 @@
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "okupamicoche-angular:serve"
"devServerTarget": "sample-oauth:serve"
},
"configurations": {
"production": {
"devServerTarget": "okupamicoche-angular:serve:production"
"devServerTarget": "sample-oauth:serve:production"
}
}
}
}
}
},
"defaultProject": "okupamicoche-angular"
}},
"defaultProject": "sample-oauth"
}

View File

@@ -16,7 +16,6 @@ exports.config = {
browserName: 'chrome'
},
directConnect: true,
SELENIUM_PROMISE_MANAGER: false,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {

View File

@@ -8,9 +8,9 @@ describe('workspace-project App', () => {
page = new AppPage();
});
it('should display welcome message', async () => {
await page.navigateTo();
expect(await page.getTitleText()).toEqual('okupamicoche-angular app is running!');
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('sample-oauth app is running!');
});
afterEach(async () => {

View File

@@ -1,11 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
async navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl);
navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
async getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText();
getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}

View File

@@ -7,6 +7,7 @@
"target": "es2018",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}

View File

@@ -9,28 +9,16 @@ module.exports = function (config) {
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/okupamicoche-angular'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/sample-oauth'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,

2126
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"name": "okupamicoche-angular",
"name": "sample-oauth",
"version": "0.0.0",
"scripts": {
"ng": "ng",
@@ -11,31 +11,33 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^10.2.4",
"@angular/common": "^10.2.4",
"@angular/compiler": "^10.2.4",
"@angular/core": "^10.2.4",
"@angular/forms": "^10.2.4",
"@angular/platform-browser": "^10.2.4",
"@angular/platform-browser-dynamic": "^10.2.4",
"@angular/router": "^10.2.4",
"angular-oauth2-oidc": "^10.0.3",
"@angular/animations": "~10.2.4",
"@angular/common": "~10.2.4",
"@angular/compiler": "~10.2.4",
"@angular/core": "~10.2.4",
"@angular/forms": "~10.2.4",
"@angular/platform-browser": "~10.2.4",
"@angular/platform-browser-dynamic": "~10.2.4",
"@angular/router": "~10.2.4",
"angular-oauth2-oidc": "^9.2.2",
"angular-oauth2-oidc-jwks": "^9.0.0",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
"tslib": "^1.14.1",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1100.7",
"@angular/cli": "^10.2.1",
"@angular/compiler-cli": "^10.2.4",
"@types/jasmine": "~3.6.0",
"@angular-devkit/build-angular": "~0.1002.1",
"@angular/cli": "~10.2.1",
"@angular/compiler-cli": "~10.2.4",
"@types/node": "^12.11.1",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.1.0",
"karma": "~5.0.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",

View File

@@ -1,35 +1,28 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AppModule]
}).compileComponents();
});
}));
it('should create the app', () => {
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
}));
it(`should have as title 'okupamicoche-angular'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('okupamicoche-angular');
});
it('should render title', () => {
it('should render link to flight in a a tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('okupamicoche-angular app is running!');
});
const compiled = fixture.debugElement.nativeElement;
console.log(compiled);
expect(compiled.querySelectorAll(' li a')[2].textContent).toContain(
'Book a Flight'
);
}));
});

View File

@@ -1,32 +1,39 @@
import { noDiscoveryAuthConfig } from './auth-no-discovery.config';
import { authConfig } from './auth.config';
import { Component } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { OAuthService, NullValidationHandler } from 'angular-oauth2-oidc';
import { Router } from '@angular/router';
import { filter } from 'rxjs/operators';
import { authCodeFlowConfig } from './auth-code-flow.config';
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
import { useHash } from '../flags';
@Component({
// tslint:disable-next-line:component-selector
selector: 'app-root',
templateUrl: './app.component.html',
selector: 'flight-app',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(private router: Router, private oauthService: OAuthService) {
this.configureCodeFlow();
// Remember the selected configuration
if (sessionStorage.getItem('flow') === 'code') {
this.configureCodeFlow();
} else {
this.configureImplicitFlow();
}
// Automatically load user profile
this.oauthService.events
.pipe(filter((e) => e.type === 'token_received'))
.subscribe((_) => {
console.log('state', this.oauthService.state);
.pipe(filter(e => e.type === 'token_received'))
.subscribe(_ => {
console.debug('state', this.oauthService.state);
this.oauthService.loadUserProfile();
});
}
private configureCodeFlow(): void {
private configureCodeFlow() {
this.oauthService.configure(authCodeFlowConfig);
this.oauthService.loadDiscoveryDocumentAndTryLogin().then((success) => {
console.error('LOGIN success=' + success);
this.oauthService.loadDiscoveryDocumentAndTryLogin().then(_ => {
if (useHash) {
this.router.navigate(['/']);
}
@@ -35,4 +42,95 @@ export class AppComponent {
// Optional
this.oauthService.setupAutomaticSilentRefresh();
}
private configureImplicitFlow() {
this.oauthService.configure(authConfig);
this.oauthService.setStorage(localStorage);
// this.oauthService.tokenValidationHandler = new JwksValidationHandler();
this.oauthService.loadDiscoveryDocumentAndTryLogin().then(_ => {
if (useHash) {
this.router.navigate(['/']);
}
});
// Optional
this.oauthService.setupAutomaticSilentRefresh();
// Display all events
this.oauthService.events.subscribe(e => {
// tslint:disable-next-line:no-console
console.debug('oauth/oidc event', e);
});
this.oauthService.events
.pipe(filter(e => e.type === 'session_terminated'))
.subscribe(e => {
// tslint:disable-next-line:no-console
console.debug('Your session has been terminated!');
});
}
//
// Below you find further examples for configuration functions
//
private configureWithoutDiscovery() {
this.oauthService.configure(noDiscoveryAuthConfig);
this.oauthService.tokenValidationHandler = new NullValidationHandler();
this.oauthService.tryLogin();
}
private configureAuth() {
//
// This method demonstrated the old API; see configureWithNewConfigApi for new one
//
// URL of the SPA to redirect the user to after login
this.oauthService.redirectUri = window.location.origin + '/index.html';
// URL of the SPA to redirect the user after silent refresh
this.oauthService.silentRefreshRedirectUri =
window.location.origin + '/silent-refresh.html';
// The SPA's id. The SPA is registerd with this id at the auth-server
this.oauthService.clientId = 'spa-demo';
// set the scope for the permissions the client should request
// The first three are defined by OIDC. The 4th is a usecase-specific one
this.oauthService.scope = 'openid profile email voucher';
// Url of the Identity Provider
this.oauthService.issuer =
'https://steyer-identity-server.azurewebsites.net/identity';
this.oauthService.tokenValidationHandler = new NullValidationHandler();
this.oauthService.events.subscribe(e => {
// tslint:disable-next-line:no-console
console.debug('oauth/oidc event', e);
});
// Load Discovery Document and then try to login the user
this.oauthService.loadDiscoveryDocument().then(doc => {
this.oauthService.tryLogin();
});
this.oauthService.events
.pipe(filter(e => e.type === 'token_expires'))
.subscribe(e => {
// tslint:disable-next-line:no-console
console.debug('received token_expires event', e);
this.oauthService.silentRefresh();
});
}
private configurePasswordFlow() {
// Set a dummy secret
// Please note that the auth-server used here demand the client to transmit a client secret, although
// the standard explicitly cites that the password flow can also be used without it. Using a client secret
// does not make sense for a SPA that runs in the browser. That's why the property is called dummyClientSecret
// Using such a dummy secreat is as safe as using no secret.
this.oauthService.dummyClientSecret = 'geheim';
}
}

View File

@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { OAuthModule } from 'angular-oauth2-oidc';
import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
@@ -9,14 +9,17 @@ import { APP_ROUTES } from './app.routes';
import { BASE_URL } from './app.tokens';
import { FlightHistoryComponent } from './flight-history/flight-history.component';
import { HomeComponent } from './home/home.component';
import { PasswordFlowLoginComponent } from './password-flow-login/password-flow-login.component';
import { SharedModule } from './shared/shared.module';
import { RouterModule, ExtraOptions } from '@angular/router';
import { CustomPreloadingStrategy } from './shared/preload/custom-preloading.strategy';
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
import { useHash } from '../flags';
const ROUTING_OPTIONS: ExtraOptions = {
// preloadingStrategy: CustomPreloadingStrategy,
useHash,
initialNavigation: 'enabled'
useHash: useHash,
initialNavigation: !useHash
};
@NgModule({
@@ -29,7 +32,7 @@ const ROUTING_OPTIONS: ExtraOptions = {
SharedModule.forRoot(),
OAuthModule.forRoot({
resourceServer: {
allowedUrls: ['http://localhost:8080/api'],
allowedUrls: ['http://www.angular.at/api'],
sendAccessToken: true
}
})
@@ -37,14 +40,15 @@ const ROUTING_OPTIONS: ExtraOptions = {
declarations: [
AppComponent,
HomeComponent,
FlightHistoryComponent
FlightHistoryComponent,
PasswordFlowLoginComponent
],
providers: [
// (useHash) ? { provide: LocationStrategy, useClass: HashLocationStrategy } : [],
// {provide: AuthConfig, useValue: authConfig },
// { provide: OAuthStorage, useValue: localStorage },
// { provide: ValidationHandler, useClass: JwksValidationHandler },
{ provide: BASE_URL, useValue: 'http://localhost:8080' }
{ provide: BASE_URL, useValue: 'http://www.angular.at' }
],
bootstrap: [AppComponent]
})

View File

@@ -1,4 +1,5 @@
import { Routes } from '@angular/router';
import { PasswordFlowLoginComponent } from './password-flow-login/password-flow-login.component';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { FlightHistoryComponent } from './flight-history/flight-history.component';
@@ -12,6 +13,10 @@ export let APP_ROUTES: Routes = [
path: 'home',
component: HomeComponent
},
{
path: 'password-flow-login',
component: PasswordFlowLoginComponent
},
{
path: 'flight-booking',
loadChildren: () =>

View File

@@ -1,3 +1,3 @@
import { InjectionToken } from '@angular/core';
export const BASE_URL = new InjectionToken<string>('http://localhost:8080');
export const BASE_URL = new InjectionToken<string>('BASE_URL');

View File

@@ -28,8 +28,8 @@ export const authCodeFlowConfig: AuthConfig = {
// Important: Request offline_access to get a refresh token
// The api scope is a usecase specific one
scope: useSilentRefreshForCodeFlow
? 'openid profile email'
: 'openid profile email offline_access',
? 'openid profile email api'
: 'openid profile email offline_access api',
// ^^ Please note that offline_access is not needed for silent refresh
// At least when using idsvr, this even prevents silent refresh

View File

@@ -0,0 +1,69 @@
import { AuthConfig } from 'angular-oauth2-oidc';
export const noDiscoveryAuthConfig: AuthConfig = {
clientId:
'1004270452653-m396kcs7jc3970turlp7ffh6bv4t1b86.apps.googleusercontent.com',
redirectUri: 'http://localhost:4200/index.html',
postLogoutRedirectUri: '',
loginUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
scope: 'openid profile email',
resource: '',
rngUrl: '',
oidc: true,
requestAccessToken: true,
options: null,
issuer: 'https://accounts.google.com',
clearHashAfterLogin: true,
tokenEndpoint: 'https://www.googleapis.com/oauth2/v4/token',
userinfoEndpoint: 'https://www.googleapis.com/oauth2/v3/userinfo',
responseType: 'token',
showDebugInformation: true,
silentRefreshRedirectUri: 'http://localhost:4200/silent-refresh.html',
silentRefreshMessagePrefix: '',
silentRefreshShowIFrame: false,
silentRefreshTimeout: 20000,
dummyClientSecret: null,
requireHttps: 'remoteOnly',
strictDiscoveryDocumentValidation: false,
jwks: {
keys: [
{
kty: 'RSA',
alg: 'RS256',
use: 'sig',
kid: '7540561fdb04b89d824a1b7b9e8849873e7cb50e',
n:
// tslint:disable-next-line: max-line-length
'sSFZrLIrXzvXBCehdPR10T-mfHWFU5ZtGzW9buI7wT_tJzZ1SRUc2l1NH92kGV9bmWRtDLjWcWFwMG7rbjX25-R-62lD1k15gQiO4bhx7gbV05e36os2vXTs0ypj9GS9y8X_2fYAnxxulMLwz4m24Ejo2tQI43-V-3Tec6cSXe0FjhRaPbGdS8GHPDKkhpJ1NHMZ38vhddIImOfvtVuz3lt_zwjBsAC6Q7PHs2GOm3KtC22DCwXMYSri4QOQcasuvTlZxIQSIksTyuH0T02IH5SJvQZSx46Vfq8BM4JP-zEEjzadoyxQPouRM6TrUeaqNv5B1f1lbH6G0G_r_ddYWQ',
e: 'AQAB'
},
{
kty: 'RSA',
alg: 'RS256',
use: 'sig',
kid: '778233e8f6f342ea09e867aad25f543adeebf372',
n:
// tslint:disable-next-line: max-line-length
'8MMxQ9F7R1zJ57QvLX-HqUlTVLLofCzZ3-lxohJr8ivJDGZoCqll7ZTNO0nGMgnPpIO-3BQLkaNGQDCpnID1vNIjClFFl0E3cN5bDX15uxCQeQDsm25fTlphpy5FkdoHCviswtrsl2KKUPeRlKqCqMjlDO27KuxIwzIPdNSqv4tseZmI-biFt2JlO9htgODrVqaawdm27t9HcWfOK_a5czRFDHWck2-ZwjbCOF9CtF1ggYm11aV0TElExXr5fgjAQdZ1yGmJvir127BRUgyIy5cpyf7VRRf2Cv7whSMoVJr4W3OK0H9vkuFLnlBiBNYQmH_eWy5U4jBfZjBqvA7Oww',
e: 'AQAB'
},
{
kty: 'RSA',
alg: 'RS256',
use: 'sig',
kid: '8ec17994394464d95b0b3d906326f1cdde8aee64',
n:
// tslint:disable-next-line: max-line-length
'w49KfvzGWVXH4vyUxvP29_QTmJfvLp4RPT1WlI6Wo2aNvn6j9vRSLDrK2CnOvvrrlUKvR-8FTcyNi9pRKXDwDhEJcyVFBJVi4PqDh0KIX_dOGYCulr5FUvU0HXQxlMWSHIsJjfGbMMUwM0p09y8KHL-kipiipzn80EpBmrI4Q3t6XOAZJSmbIPaGZJDjyoWWV0TDdVDBMfkqII6tOOB7Ha189AZjz7FHYXR9CIc0Jm6rFy0tVpdHFEG3ptcNQEDQ5ghyMM4PDM4ZmQ5uk3WgHVqnpdmGEfKekLwmYFWgnI-ux_MabltIxr9TE1qubEmebM64rOusHBF0mSbEwggbyw',
e: 'AQAB'
}
]
},
customQueryParams: null,
silentRefreshIFrameName: 'angular-oauth-oidc-silent-refresh-iframe',
timeoutFactor: 0.75,
sessionCheckIntervall: 3000,
sessionCheckIFrameName: 'angular-oauth-oidc-check-session-iframe',
disableAtHashCheck: false,
skipSubjectCheck: false
};

View File

@@ -0,0 +1,27 @@
// This api will come in the next version
import { AuthConfig } from 'angular-oauth2-oidc';
export const authPasswordFlowConfig: AuthConfig = {
// Url of the Identity Provider
issuer: 'https://steyer-identity-server.azurewebsites.net/identity',
// URL of the SPA to redirect the user to after login
redirectUri: window.location.origin + '/index.html',
// URL of the SPA to redirect the user after silent refresh
silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html',
// The SPA's id. The SPA is registerd with this id at the auth-server
clientId: 'demo-resource-owner',
dummyClientSecret: 'geheim',
// set the scope for the permissions the client should request
// The first three are defined by OIDC. The 4th is a usecase-specific one
scope: 'openid profile email voucher',
showDebugInformation: true,
oidc: false
};

34
src/app/auth.config.ts Normal file
View File

@@ -0,0 +1,34 @@
// This api will come in the next version
import { AuthConfig } from 'angular-oauth2-oidc';
export const authConfig: AuthConfig = {
// Url of the Identity Provider
issuer: 'https://idsvr4.azurewebsites.net',
// URL of the SPA to redirect the user to after login
// redirectUri: window.location.origin
// + ((localStorage.getItem('useHashLocationStrategy') === 'true')
// ? '/#/index.html'
// : '/index.html'),
redirectUri: window.location.origin + '/index.html',
// URL of the SPA to redirect the user after silent refresh
silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html',
// The SPA's id. The SPA is registerd with this id at the auth-server
clientId: 'implicit',
// set the scope for the permissions the client should request
// The first three are defined by OIDC. The 4th is a usecase-specific one
scope: 'openid profile email api',
// silentRefreshShowIFrame: true,
showDebugInformation: true,
sessionChecksEnabled: true
// timeoutFactor: 0.01,
};

View File

@@ -0,0 +1,28 @@
// This api will come in the next version
import { AuthConfig } from 'angular-oauth2-oidc';
export const googleAuthConfig: AuthConfig = {
// Url of the Identity Provider
issuer: 'https://accounts.google.com',
// URL of the SPA to redirect the user to after login
redirectUri: window.location.origin + '/index.html',
// URL of the SPA to redirect the user after silent refresh
silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html',
// The SPA's id. The SPA is registerd with this id at the auth-server
clientId:
'1004270452653-m396kcs7jc3970turlp7ffh6bv4t1b86.apps.googleusercontent.com',
strictDiscoveryDocumentValidation: false,
// set the scope for the permissions the client should request
// The first three are defined by OIDC. The 4th is a usecase-specific one
scope: 'openid profile email',
showDebugInformation: true,
sessionChecksEnabled: true
};

View File

@@ -27,6 +27,34 @@
</div>
</div>
<div class="panel panel-default">
<div class="panel-body">
<h2>Login with Implicit Flow</h2>
<p>
<button class="btn btn-default" (click)="loginImplicit()">Login</button>
<button class="btn btn-default" (click)="logout()">Logout</button>
</p>
<b>Username/Password:</b> max/geheim
</div>
</div>
<div class="panel panel-default">
<div class="panel-body">
<h2>Login with Implicit Flow in popup</h2>
<p>
<button class="btn btn-default" (click)="loginImplicitInPopup()">
Login
</button>
<button class="btn btn-default" (click)="logout()">Logout</button>
</p>
<p><b>Username/Password:</b> max/geheim</p>
<p>
<b>Note:</b> When using IE, some security settings block the communication
with popups. This prevents that this feature works.
</p>
</div>
</div>
<div class="panel panel-default">
<div class="panel-body">
<h2>Login with Code Flow</h2>

View File

@@ -1,3 +1,4 @@
import { authConfig } from '../auth.config';
import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { authCodeFlowConfig } from '../auth-code-flow.config';
@@ -7,7 +8,7 @@ import { ActivatedRoute } from '@angular/router';
templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
loginFailed = false;
loginFailed: boolean = false;
userProfile: object;
usePopup: boolean;
login: false;
@@ -17,9 +18,9 @@ export class HomeComponent implements OnInit {
private oauthService: OAuthService
) {}
ngOnInit(): void {
ngOnInit() {
this.route.params.subscribe(p => {
this.login = p.login;
this.login = p['login'];
});
// This would directly (w/o user interaction) redirect the user to the
@@ -33,7 +34,29 @@ export class HomeComponent implements OnInit {
*/
}
async loginCode(): Promise<void> {
async loginImplicit() {
// Tweak config for implicit flow
this.oauthService.configure(authConfig);
await this.oauthService.loadDiscoveryDocument();
sessionStorage.setItem('flow', 'implicit');
this.oauthService.initLoginFlow('/some-state;p1=1;p2=2?p3=3&p4=4');
// the parameter here is optional. It's passed around and can be used after logging in
}
async loginImplicitInPopup() {
// Tweak config for implicit flow
this.oauthService.configure(authConfig);
await this.oauthService.loadDiscoveryDocument();
sessionStorage.setItem('flow', 'implicit');
this.oauthService.initLoginFlowInPopup().then(() => {
this.loadUserProfile();
});
// the parameter here is optional. It's passed around and can be used after logging in
}
async loginCode() {
// Tweak config for code flow
this.oauthService.configure(authCodeFlowConfig);
await this.oauthService.loadDiscoveryDocument();
@@ -43,7 +66,7 @@ export class HomeComponent implements OnInit {
// the parameter here is optional. It's passed around and can be used after logging in
}
async loginCodeInPopup(): Promise<void> {
async loginCodeInPopup() {
// Tweak config for code flow
this.oauthService.configure(authCodeFlowConfig);
await this.oauthService.loadDiscoveryDocument();
@@ -54,7 +77,7 @@ export class HomeComponent implements OnInit {
});
}
logout(): void {
logout() {
// this.oauthService.logOut();
this.oauthService.revokeTokenAndLogout();
}
@@ -63,19 +86,19 @@ export class HomeComponent implements OnInit {
this.oauthService.loadUserProfile().then(up => (this.userProfile = up));
}
get givenName(): any {
const claims = this.oauthService.getIdentityClaims();
if (!claims) { return null; }
get givenName() {
var claims = this.oauthService.getIdentityClaims();
if (!claims) return null;
return claims['given_name'];
}
get familyName(): any {
const claims = this.oauthService.getIdentityClaims();
if (!claims) { return null; }
get familyName() {
var claims = this.oauthService.getIdentityClaims();
if (!claims) return null;
return claims['family_name'];
}
refresh(): void {
refresh() {
this.oauthService.oidc = true;
if (
@@ -84,12 +107,12 @@ export class HomeComponent implements OnInit {
) {
this.oauthService
.refreshToken()
.then(info => console.log('refresh ok', info))
.then(info => console.debug('refresh ok', info))
.catch(err => console.error('refresh error', err));
} else {
this.oauthService
.silentRefresh()
.then(info => console.log('silent refresh ok', info))
.then(info => console.debug('silent refresh ok', info))
.catch(err => console.error('silent refresh error', err));
}
}
@@ -99,7 +122,7 @@ export class HomeComponent implements OnInit {
localStorage.setItem('requestAccessToken', '' + value);
}
get requestAccessToken(): boolean {
get requestAccessToken() {
return this.oauthService.requestAccessToken;
}
@@ -111,23 +134,23 @@ export class HomeComponent implements OnInit {
}
}
get useHashLocationStrategy(): boolean {
get useHashLocationStrategy() {
return localStorage.getItem('useHashLocationStrategy') === 'true';
}
get id_token(): string {
get id_token() {
return this.oauthService.getIdToken();
}
get access_token(): string {
get access_token() {
return this.oauthService.getAccessToken();
}
get id_token_expiration(): number {
get id_token_expiration() {
return this.oauthService.getIdTokenExpiration();
}
get access_token_expiration(): number {
get access_token_expiration() {
return this.oauthService.getAccessTokenExpiration();
}
}

View File

@@ -0,0 +1,47 @@
<h1 *ngIf="!givenName">Welcome!</h1>
<h1 *ngIf="givenName">Welcome, {{ givenName }} {{ familyName }}!</h1>
<div class="panel panel-default">
<div class="panel-body">
<p>Login with Username/Password</p>
<p style="color:red; font-weight:bold" *ngIf="loginFailed">
Login wasn't successfull.
</p>
<div class="form-group">
<label>Username</label>
<input class="form-control" [(ngModel)]="userName" />
</div>
<div class="form-group">
<label>Password</label>
<input class="form-control" type="password" [(ngModel)]="password" />
</div>
<div class="form-group">
<button class="btn btn-default" (click)="loginWithPassword()">
Login
</button>
<button class="btn btn-default" (click)="logout()">Logout</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-body"><b>Username/Password:</b> max/geheim</div>
</div>
<div class="panel panel-default">
<div class="panel-body">
<p><b>access_token_expiration:</b> {{ access_token_expiration }}</p>
</div>
</div>
<div class="panel panel-default">
<div class="panel-body">
<p><b>access_token:</b> {{ access_token }}</p>
<div *ngIf="userProfile">
<b>user profile:</b>
<pre>{{ userProfile | json }}</pre>
</div>
</div>
</div>

View File

@@ -0,0 +1,69 @@
import { authPasswordFlowConfig } from '../auth-password-flow.config';
import { OAuthService } from 'angular-oauth2-oidc';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-password-flow-login',
templateUrl: './password-flow-login.component.html'
})
export class PasswordFlowLoginComponent implements OnInit {
userName: string;
password: string;
loginFailed: boolean = false;
userProfile: object;
constructor(private oauthService: OAuthService) {
// Tweak config for password flow
// This is just needed b/c this demo uses both,
// implicit flow as well as password flow
this.oauthService.configure(authPasswordFlowConfig);
this.oauthService.loadDiscoveryDocument();
}
ngOnInit() {}
loadUserProfile(): void {
this.oauthService.loadUserProfile().then(up => (this.userProfile = up));
}
get access_token() {
return this.oauthService.getAccessToken();
}
get access_token_expiration() {
return this.oauthService.getAccessTokenExpiration();
}
get givenName() {
var claims = this.oauthService.getIdentityClaims();
if (!claims) return null;
return claims['given_name'];
}
get familyName() {
var claims = this.oauthService.getIdentityClaims();
if (!claims) return null;
return claims['family_name'];
}
loginWithPassword() {
this.oauthService
.fetchTokenUsingPasswordFlowAndLoadUserProfile(
this.userName,
this.password
)
.then(() => {
console.debug('successfully logged in');
this.loginFailed = false;
})
.catch(err => {
console.error('error logging in', err);
this.loginFailed = true;
});
}
logout() {
this.oauthService.logOut(true);
}
}

View File

@@ -6,7 +6,7 @@ import { OAuthService } from 'angular-oauth2-oidc';
export class AuthGuard implements CanActivate {
constructor(private router: Router, private oauthService: OAuthService) {}
canActivate(): boolean {
canActivate() {
if (
this.oauthService.hasValidAccessToken() &&
this.oauthService.hasValidIdToken()

View File

@@ -1,16 +1,8 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -1,13 +1,14 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>OkupamicocheAngular</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
<head>
<meta charset="utf-8" />
<title>Sample</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<flight-app></flight-app>
</body>
</html>

View File

@@ -1,12 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { AppModule } from './app/app.module';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@@ -11,53 +11,41 @@
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE11 requires the following for NgClass support on SVG elements */
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
/** IE10 and IE11 requires the following to support `@angular/animation`. */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/** Evergreen browsers require these. **/
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/**
* Date, currency, decimal and percent pipes.
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
*/
// import 'intl'; // Run `npm install --save intl`.
/**
* Need to import at least one locale-data with intl.
*/
// import 'intl/locale-data/jsonp/en';

View File

@@ -7,12 +7,7 @@ import {
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[];
<T>(id: string): T;
};
};
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(

View File

@@ -4,10 +4,6 @@
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
@@ -20,10 +16,5 @@
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}