modules/firebase-connector/src/authMethods/vk.ts
Vk firebase auth servicevk.com
is not among standard auth providers in firebase, so we provide our own solution for
this. To get acquainted with the flow, please, read this article.
In order to avoid creation of http server, vkAuthService works only with firebase and not with REST API.
So, we strongly recommend to set redirect url to be a current location, thus one can get a token on a client side.
Internal work of a service one can found in source tab.
Usage:
VkConfigService
declaration
//vkAuthConfig.service.ts
import {Injectable} from "@angular/core";
import {VkAuthConfig, VkConfig, PopupConfig} from "@nodeart/firebase-connector";
\@Injectable()
export class VkConfiguration implements VkAuthConfig {
public vkConfig : VkConfig = {
client_id: 'app_id',
display: 'popup',
scope: ['friends'],
response_type: 'token',
v: 5.63
};
public popupConfig: PopupConfig = {
location: 'no',
height: 600,
width: 600
};
public cleanUp: boolean = true;
public dbPath: string = 'auth/vk';
constructor() { }
}
VkConfigService
register
//someModule.module.ts
import {NgModule} from "@angular/core";
import {VkConfiguration} from "./vkAuthConfig.service";
\@NgModule({
providers: [
{provide: 'VkAuthConfig', useClass: VkConfiguration}
]
})
export class SomeModule { }
Server side or firebase cloud functions code:
'use strict';
const admin = require('firebase-admin');
const authWithVk = admin.database().ref('auth/vk');
const listener = (ref, snapshot) => {
const key = snapshot.key,
val = snapshot.val();
if (!val.processed) {
admin.auth()
.createCustomToken(val['access_token'])
.then(token => {
const data = Object.assign(val, {
access_token: token,
expires_in: null,
processed: true
});
ref.child(key).set(data);
return data
})
.then(data => console.log(`custom token generated = ${JSON.stringify(data)}`))
}
};
authWithVk.on('child_added', snapshot => listener(authWithVk, snapshot));
constructor(config: VkAuthConfig)
|
login |
login()
|
Login method
Returns:
any
|
Public createUrl |
createUrl(obj: Object, starter: string)
|
utility function that converts an object to url
Returns:
string
|
Private authorize |
authorize: |
Default value: https://oauth.vk.com/authorize
|
VkAuth page base url; |
name |
name: |
Default value: Vk
|
Name of auth method |
import { Injectable, Inject } from '@angular/core';
import { AuthMethod } from './auth-method';
import { Observable, Subject } from 'rxjs';
import * as firebase from 'firebase';
/**
* Vk firebase auth service
* `vk.com` is not among standard auth providers in firebase, so we provide our own solution for
* this. To get acquainted with the flow, please, read [this](https://vk.com/dev/implicit_flow_user) article.
*
* In order to avoid creation of http server, vkAuthService works only with firebase and not with REST API.
* So, we strongly recommend to set redirect url to be a current location, thus one can get a token on a client side.
*
* Internal work of a service one can found in source tab.
*
* **Usage:**
*
* `VkConfigService` declaration
* ```typescript
* //vkAuthConfig.service.ts
* import {Injectable} from "@angular/core";
* import {VkAuthConfig, VkConfig, PopupConfig} from "@nodeart/firebase-connector";
*
* \@Injectable()
* export class VkConfiguration implements VkAuthConfig {
* public vkConfig : VkConfig = {
* client_id: 'app_id',
* display: 'popup',
* scope: ['friends'],
* response_type: 'token',
* v: 5.63
* };
* public popupConfig: PopupConfig = {
* location: 'no',
* height: 600,
* width: 600
* };
* public cleanUp: boolean = true;
* public dbPath: string = 'auth/vk';
* constructor() { }
* }
* ```
*
* `VkConfigService` register
*
* ```typescript
* //someModule.module.ts
* import {NgModule} from "@angular/core";
* import {VkConfiguration} from "./vkAuthConfig.service";
* \@NgModule({
* providers: [
* {provide: 'VkAuthConfig', useClass: VkConfiguration}
* ]
* })
* export class SomeModule { }
* ```
*
* Server side or [firebase cloud functions](https://firebase.google.com/docs/functions/) code:
* ```javascript
* 'use strict';
*
* const admin = require('firebase-admin');
* const authWithVk = admin.database().ref('auth/vk');
*
* const listener = (ref, snapshot) => {
* const key = snapshot.key,
* val = snapshot.val();
*
* if (!val.processed) {
* admin.auth()
* .createCustomToken(val['access_token'])
* .then(token => {
* const data = Object.assign(val, {
* access_token: token,
* expires_in: null,
* processed: true
* });
* ref.child(key).set(data);
* return data
* })
* .then(data => console.log(`custom token generated = ${JSON.stringify(data)}`))
* }
* };
*
* authWithVk.on('child_added', snapshot => listener(authWithVk, snapshot));
* ```
*
*/
@Injectable()
export class VkAuth implements AuthMethod {
/**
* Name of auth method
*/
name: string = 'Vk';
/**
* VkAuth page base url;
*/
private authorize: string = 'https://oauth.vk.com/authorize';
constructor(
@Inject('VkAuthConfig') private config : VkAuthConfig
) {
this.config.vkConfig.redirect_uri = this.config.vkConfig.redirect_uri || window.location.href
}
/**
* Login method
*/
login(): Promise<any> {
/**
* Create a popup window and get its reference.
* @type {Window}
*/
const vkAuthWindow = window.open(
this.createUrl(this.config.vkConfig, this.authorize),
'_blank',
Object.keys(this.config.popupConfig).reduce(
(acc, key) => acc += `${key}=${this.config.popupConfig[key]},`,
''
)
);
return new Promise((resolve, reject) => {
/**
* flow: ---window.origin: blank----window.origin Error(cross-origin)----window.orogin OK
*/
let dbRef;
const stopper = new Subject();
/**
* each 1 second trigger an interval
*/
Observable.interval(1000)
/**
* that is mapped to location
*/
.map(() => vkAuthWindow.location)
/**
* path execution forward only if there is a hash in location (will block execution of unloaded window)
*/
.filter(location => !!location.hash)
/**
* path forward only if there is an access_token in hash,
* otherwise throw an Error
*/
.map(location => {
if (location.hash.indexOf('access_token') !== -1) {
return location.hash;
} else {
throw new Error('no_token')
}
})
/**
* stop interval execution after subject will trigger next function
*/
.takeUntil(stopper)
/**
* handle all errors that happen while execution and start observable again on failure
* (will block cross-origin) errors
*/
.catch((err, outputObs) => outputObs)
/**
* transform hash to object
*/
.map(hash =>
hash.replace('#', '')
.split('&')
.reduce((acc, elem) => {
const [key, val] = elem.split('=');
acc[decodeURIComponent(key)] = decodeURIComponent(val);
return acc;
}, {})
)
/**
* once we have reached subscribe, trigger next on subject and block interval execution
*/
.subscribe(
auth => stopper.next(auth),
err => console.log(err, 'err')
);
/**
* set object with access token and uid to firebase database
*/
stopper.flatMap((obj: Object) => {
const db = firebase['database']();
dbRef = db.ref(this.config.dbPath).push();
dbRef.set(obj);
return Observable.fromPromise(dbRef.once('child_changed') as Promise<any>);
})
/**
* once the data is changed, get an access token
*/
.map(snapshot => snapshot.val())
/**
* clean up
*/
.do(() => {
if (this.config.cleanUp) {
dbRef.remove();
}
})
/**
* get only not null values
*/
.filter(val => !!val)
/**
* take the value only once
*/
.first()
/**
* get only access token field
*/
.map(data => typeof data === 'object' ? data['token'] : data)
/**
* auth with custom token
*/
.flatMap(token => Observable.fromPromise(firebase['auth']().signInWithCustomToken(token) as Promise<any>))
/**
* close popup window
*/
.do(() => vkAuthWindow.close())
.subscribe(
auth => resolve(auth),
err => reject(err)
)
});
}
/**
* utility function that converts an object to url
* @returns {string}
*/
public createUrl(obj : Object, starter?: string): string {
const join = (arr: Array<string>): string => arr.join(',');
return Object.keys(obj)
.reduce(
(acc: string, key: string, i: number): string => acc += `${i === 0 ? '?' : '&'}${key}=${
Array.isArray(obj[key]) ?
join(obj[key]) :
obj[key]
}`,
starter || ''
)
}
}
/**
* Utility interfaces
*/
export interface VkAuthConfig {
popupConfig: PopupConfig;
vkConfig: VkConfig;
dbPath: string;
cleanUp: boolean;
}
export interface VkConfig {
client_id: string;
redirect_uri?: string;
scope: Array<string>;
display: string;
response_type: string;
v: string | number;
state?: string;
revoke?: number;
}
export interface PopupConfig {
location: string;
[key: string] : any;
}