Skip to content

Commit

Permalink
Added the userInfo card
Browse files Browse the repository at this point in the history
  • Loading branch information
tikurahul committed Jun 13, 2017
1 parent 5d74e7f commit a497ce6
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 381 deletions.
97 changes: 86 additions & 11 deletions app.ts
@@ -1,16 +1,45 @@
import { AuthFlow, AuthStateEmitter } from './flow';
import { log } from './logger';
import {AuthFlow, AuthStateEmitter} from './flow';
import {log} from './logger';

const SIGN_IN = 'Sign-In';
const SIGN_OUT = 'Sign-Out';

interface SnackBarOptions {
message: string;
timeout?: number;
actionHandler?: (event: any) => void;
actionText?: string;
}

interface UserInfo {
name: string;
given_name: string;
family_name: string;
picture: string;
}

export class App {
private authFlow: AuthFlow = new AuthFlow();
private handleSignIn = document.querySelector('#handle-sign-in')!;
private userInfo: UserInfo|null = null;

constructor() {
this.handleSignIn.textContent = SIGN_IN;
private handleSignIn =
document.querySelector('#handle-sign-in') as HTMLElement;

private fetchUserInfo =
document.querySelector('#handle-user-info') as HTMLElement;

private userCard = document.querySelector('#user-info') as HTMLElement;

private userProfileImage =
document.querySelector('#user-profile-image') as HTMLImageElement;

private userName = document.querySelector('#user-name') as HTMLElement;

private snackbarContainer: any =
document.querySelector('#appauth-snackbar') as HTMLElement;

constructor() {
this.initializeUi();
this.handleSignIn.addEventListener('click', (event) => {
if (this.handleSignIn.textContent === SIGN_IN) {
this.signIn();
Expand All @@ -20,25 +49,71 @@ export class App {
event.preventDefault();
});

this.authFlow.authStateEmitter.on(AuthStateEmitter.ON_AUTHORIZATION_RESPONSE, () => {
this.handleSignIn.textContent = SIGN_OUT;
this.fetchUserInfo.addEventListener('click', () => {
this.authFlow.performWithFreshTokens().then(accessToken => {
let request =
new Request('https://www.googleapis.com/oauth2/v3/userinfo', {
headers: new Headers({'Authorization': `Bearer ${accessToken}`}),
method: 'GET',
cache: 'no-cache'
});

fetch(request)
.then(result => result.json())
.then(user => {
log('User Info ', user);
this.userInfo = user;
this.updateUi();
})
.catch(error => {
log('Something bad happened ', error);
});
});
});

this.authFlow.authStateEmitter.on(
AuthStateEmitter.ON_TOKEN_RESPONSE, () => {
this.updateUi();
});
}

signIn(username?: string): Promise<void> {
if (!this.authFlow.loggedIn()) {
return this.authFlow.fetchServiceConfiguration()
.then(() => this.authFlow.makeAuthorizationRequest(username));
return this.authFlow.fetchServiceConfiguration().then(
() => this.authFlow.makeAuthorizationRequest(username));
} else {
return Promise.resolve();
}
}

signOut() {
this.authFlow.signOut();
private initializeUi() {
this.handleSignIn.textContent = SIGN_IN;
this.fetchUserInfo.style.display = 'none';
this.userCard.style.display = 'none';
}

// update ui post logging in.
private updateUi() {
this.handleSignIn.textContent = SIGN_OUT;
this.fetchUserInfo.style.display = '';
if (this.userInfo) {
this.userProfileImage.src = `${this.userInfo.picture}?sz=96`;
this.userName.textContent = this.userInfo.name;
this.showSnackBar(
{message: `Welcome ${this.userInfo.name}`, timeout: 4000});
this.userCard.style.display = '';
}
}

private showSnackBar(data: SnackBarOptions) {
this.snackbarContainer.MaterialSnackbar.showSnackbar(data);
}

signOut() {
this.authFlow.signOut();
this.userInfo = null;
this.initializeUi();
}
}

log('Init complete');
Expand Down
88 changes: 45 additions & 43 deletions flow.ts
@@ -1,19 +1,19 @@
import { AuthorizationRequest } from '@openid/appauth/built/authorization_request';
import { AuthorizationNotifier, AuthorizationRequestHandler, AuthorizationRequestResponse, BUILT_IN_PARAMETERS } from '@openid/appauth/built/authorization_request_handler';
import { AuthorizationResponse } from '@openid/appauth/built/authorization_response';
import { AuthorizationServiceConfiguration } from '@openid/appauth/built/authorization_service_configuration';
import { NodeBasedHandler } from '@openid/appauth/built/node_support/node_request_handler';
import { NodeRequestor } from '@openid/appauth/built/node_support/node_requestor';
import { GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN, TokenRequest } from '@openid/appauth/built/token_request';
import { BaseTokenRequestHandler, TokenRequestHandler } from '@openid/appauth/built/token_request_handler';
import { TokenError, TokenResponse } from '@openid/appauth/built/token_response';
import {AuthorizationRequest} from '@openid/appauth/built/authorization_request';
import {AuthorizationNotifier, AuthorizationRequestHandler, AuthorizationRequestResponse, BUILT_IN_PARAMETERS} from '@openid/appauth/built/authorization_request_handler';
import {AuthorizationResponse} from '@openid/appauth/built/authorization_response';
import {AuthorizationServiceConfiguration} from '@openid/appauth/built/authorization_service_configuration';
import {NodeBasedHandler} from '@openid/appauth/built/node_support/node_request_handler';
import {NodeRequestor} from '@openid/appauth/built/node_support/node_requestor';
import {GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN, TokenRequest} from '@openid/appauth/built/token_request';
import {BaseTokenRequestHandler, TokenRequestHandler} from '@openid/appauth/built/token_request_handler';
import {TokenError, TokenResponse} from '@openid/appauth/built/token_response';
import EventEmitter = require('events');

import { log } from './logger';
import { StringMap } from "@openid/appauth/built/types";
import {log} from './logger';
import {StringMap} from '@openid/appauth/built/types';

export class AuthStateEmitter extends EventEmitter {
static ON_AUTHORIZATION_RESPONSE = 'on_authorization_response';
static ON_TOKEN_RESPONSE = 'on_token_response';
}

/* the Node.js based HTTP client. */
Expand All @@ -24,7 +24,7 @@ const openIdConnectUrl = 'https://accounts.google.com';

/* example client configuration */
const clientId =
'511828570984-dhnshqcpegee66hgnp754dupe8sbas18.apps.googleusercontent.com';
'511828570984-dhnshqcpegee66hgnp754dupe8sbas18.apps.googleusercontent.com';
const redirectUri = 'http://localhost:8000';
const scope = 'openid';
// TODO(rahulrav@): Figure out a way to get rid of this
Expand All @@ -37,10 +37,10 @@ export class AuthFlow {
readonly authStateEmitter: AuthStateEmitter;

// state
private configuration: AuthorizationServiceConfiguration | null;
private configuration: AuthorizationServiceConfiguration|null;

private refreshToken: string | null;
private accessTokenResponse: TokenResponse | null;
private refreshToken: string|null;
private accessTokenResponse: TokenResponse|null;

constructor() {
this.notifier = new AuthorizationNotifier();
Expand All @@ -54,21 +54,23 @@ export class AuthFlow {
this.notifier.setAuthorizationListener((request, response, error) => {
log('Authorization request complete ', request, response, error);
if (response) {
this.authStateEmitter.emit(AuthStateEmitter.ON_AUTHORIZATION_RESPONSE);
this.makeRefreshTokenRequest(response.code)
.then(result => this.performWithFreshTokens()
.then(() => log('All done.')));
.then(result => this.performWithFreshTokens())
.then(() => {
this.authStateEmitter.emit(AuthStateEmitter.ON_TOKEN_RESPONSE);
log('All Done.');
})
}
});
}

fetchServiceConfiguration(): Promise<void> {
return AuthorizationServiceConfiguration
.fetchFromIssuer(openIdConnectUrl, requestor)
.then(response => {
log('Fetched service configuration', response);
this.configuration = response;
});
.fetchFromIssuer(openIdConnectUrl, requestor)
.then(response => {
log('Fetched service configuration', response);
this.configuration = response;
});
}

makeAuthorizationRequest(username?: string) {
Expand All @@ -77,20 +79,20 @@ export class AuthFlow {
return;
}

const extras: StringMap = { 'prompt': 'consent', 'access_type': 'offline' };
const extras: StringMap = {'prompt': 'consent', 'access_type': 'offline'};
if (username) {
extras['login_hint'] = username;
}

// create a request
const request = new AuthorizationRequest(
clientId, redirectUri, scope, AuthorizationRequest.RESPONSE_TYPE_CODE,
undefined /* state */, extras);
clientId, redirectUri, scope, AuthorizationRequest.RESPONSE_TYPE_CODE,
undefined /* state */, extras);

log('Making authorization request ', this.configuration, request);

this.authorizationHandler.performAuthorizationRequest(
this.configuration, request);
this.configuration, request);
}

private makeRefreshTokenRequest(code: string): Promise<void> {
Expand All @@ -100,17 +102,17 @@ export class AuthFlow {
}
// use the code to make the token request.
let request = new TokenRequest(
clientId, redirectUri, GRANT_TYPE_AUTHORIZATION_CODE, code, undefined,
{ 'client_secret': clientSecret });
clientId, redirectUri, GRANT_TYPE_AUTHORIZATION_CODE, code, undefined,
{'client_secret': clientSecret});

return this.tokenHandler.performTokenRequest(this.configuration, request)
.then(response => {
log(`Refresh Token is ${response.refreshToken}`);
this.refreshToken = response.refreshToken;
this.accessTokenResponse = response;
return response;
})
.then(() => { });
.then(response => {
log(`Refresh Token is ${response.refreshToken}`);
this.refreshToken = response.refreshToken;
this.accessTokenResponse = response;
return response;
})
.then(() => {});
}

loggedIn(): boolean {
Expand All @@ -136,12 +138,12 @@ export class AuthFlow {
return Promise.resolve(this.accessTokenResponse.accessToken);
}
let request = new TokenRequest(
clientId, redirectUri, GRANT_TYPE_REFRESH_TOKEN, undefined,
this.refreshToken, { 'client_secret': clientSecret });
clientId, redirectUri, GRANT_TYPE_REFRESH_TOKEN, undefined,
this.refreshToken, {'client_secret': clientSecret});
return this.tokenHandler.performTokenRequest(this.configuration, request)
.then(response => {
this.accessTokenResponse = response;
return response.accessToken;
});
.then(response => {
this.accessTokenResponse = response;
return response.accessToken;
});
}
}
54 changes: 32 additions & 22 deletions index.html
@@ -1,39 +1,49 @@
<!DOCTYPE html>
<html class="mdc-typography">
<html>

<head>
<title>AppAuth in Electron Shell</title>
<link rel="stylesheet" href="node_modules/material-components-web/dist/material-components-web.min.css">
<title>AppAuth-JS with Electron</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="node_modules/material-design-lite/material.min.css">
<link rel="stylesheet" href="styles.css">
</head>

<body>
<div class="root">
<div id="app-auth" class="mdc-card">
<div class="app-auth-card mdl-card mdl-shadow--2dp">
<section class="mdc-card__primary">
<h1 class="mdc-card__title mdc-card__title--large">AppAuthJS</h1>
<h2 class="mdc-card__subtitle">This example shows how one can integrate AppAuthJS and Electron.</h2>
<div class="mdl-card__title">
<h1 class="mdl-card__title-text">AppAuth-JS</h1>
</div>
<div class="flex-container">
<img class="appauth-logo" src="assets/appauth_circle.svg" />
</div>
</section>
<section class="mdc-card__media">
<img src="assets/appauth_circle.svg" />
</section>
<section class="mdc-card__supporting-text">
For more information look source of the application at the following <a href="#">link</a>.
</section>
<section class="mdc-card__actions">
<button id="handle-sign-in" class="mdc-button mdc-button--compact mdc-card__action"></button>
<section class="mdl-card__supporting-text">
This example shows how one can integrate AppAuthJS and Electron. For more information look source of the application at the
following <a href="https://github.com/openid/AppAuth-JS">https://github.com/openid/AppAuth-JS</a>.
</section>
<div class="mdl-card__actions mdl-card--border">
<button id="handle-sign-in" class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect">Placeholder</button>
<button id="handle-user-info" class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect">User Info</button>
</div>
</div>
<div id="user-info" class="app-auth-card mdl-card mdl-shadow--2dp">
<div class="flex-container">
<img id="user-profile-image">
<div id="user-name" class="mdl-typography--headline"></div>
</div>
</div>
<!-- snackbar -->
<div id="appauth-snackbar" class="mdl-js-snackbar mdl-snackbar">
<div class="mdl-snackbar__text"></div>
<button class="mdl-snackbar__action" type="button"></button>
</div>
</div>

<script src="node_modules/material-components-web/dist/material-components-web.js"></script>
<script>
mdc.autoInit()
</script>
<script>
document.addEventListener('DOMContentLoaded', () => {
require('./built/app');
});
<script src="node_modules/material-design-lite/material.min.js"></script>
<script type="text/javascript">
require('./built/app');
</script>
</body>

Expand Down
12 changes: 6 additions & 6 deletions index.ts
@@ -1,14 +1,14 @@
import { app, BrowserWindow } from 'electron';
import {app, BrowserWindow} from 'electron';
import url = require('url');
import path = require('path');
import { log } from './logger';
import {log} from './logger';

// retain a reference to the window, otherwise it gets gc-ed
let w: Electron.BrowserWindow | null = null;
let w: Electron.BrowserWindow|null = null;

function createWindow(): Electron.BrowserWindow {
log('Creating window.');
w = new BrowserWindow({ width: 1920, height: 1080 });
w = new BrowserWindow({width: 1920, height: 1080});
w.loadURL(url.format({
pathname: path.join(path.dirname(__dirname), 'index.html'),
protocol: 'file:',
Expand All @@ -35,8 +35,8 @@ app.on('window-all-closed', () => {
});

app.on('activate', () => {
log('Activating');
if (!!w) {
if (window === null) {
log('Creating a new window');
w = createWindow();
}
});

0 comments on commit a497ce6

Please sign in to comment.