Skip to content

Instantly share code, notes, and snippets.

@tiarebalbi
Last active April 12, 2018 11:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tiarebalbi/62bb6dd94454cadfc09367275c9f5707 to your computer and use it in GitHub Desktop.
Save tiarebalbi/62bb6dd94454cadfc09367275c9f5707 to your computer and use it in GitHub Desktop.
// @flow
import type { AuthConfig, AuthTokenType, Storage } from '../types';
import axios, { Axios, AxiosXHRConfigBase } from 'axios';
import { AuthenticationError } from './authenticationError';
import { decode } from '../helpers/htmlEntitiesEncoding';
import { LocalStorage } from '../helpers/storage';
import Authentication from './authentication';
import ErrorMapping from './errorMapping';
export default class AuthenticationService {
client: Axios;
config: AuthConfig;
storage: Storage;
_storageEntries = {
authenticationToken: 'authenticationToken',
};
_errors = new ErrorMapping();
constructor(authConfig: AuthConfig) {
const config: AxiosXHRConfigBase<*> = {
timeout: 1500,
baseURL: 'http://localhost:8080',
...authConfig,
};
this.config = authConfig;
this.client = axios.create(config);
this.storage = new LocalStorage({
version: { scope: 1 },
});
if (!this.config.basicKey) {
const errorCode = 601;
throw new AuthenticationError(this._errors.get(errorCode), errorCode);
}
}
/**
* Method to authenticate a user to the system using OAuth 2.0
*
* @param username from the user
* @param password from the user
* @returns {Promise<*>}
*/
async login(username: string, password: string): Promise<AuthTokenType> {
try {
const endpoint = '/oauth/token?grant_type=password';
const data = `username=${username}&password=${password}`;
const response = await this.client.post(endpoint, data, {
headers: {
Authorization: `Basic ${this.config.basicKey}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
});
const authenticationData = response.data;
this._saveAuthenticationDetails(authenticationData);
return authenticationData;
} catch (error) {
let message = error.message;
let errorCode = error.response.status;
if (error.response.data) {
message = decode(error.response.data[ 'error_description' ]);
}
throw new AuthenticationError(message, errorCode);
}
}
/**
* Method to logout and expire tokens from the user
* @returns {Promise<void>}
*/
async logout(): Promise<void> {
try {
const key = this._storageEntries.authenticationToken;
this.storage.remove(key);
const endpoint = '/api/v1/sessoes';
await this.client.delete(endpoint, {
headers: {
Authorization: this.getAuthenticationDetails().getBearerToken(),
'Content-Type': 'application/x-www-form-urlencoded',
},
});
} catch (e) {
}
}
/**
* Method to refresh token if authentication details are present
* @returns {Promise<void>}
*/
async refreshToken(): Promise<void> {
const key = this._storageEntries.authenticationToken;
try {
const t = this.getAuthenticationDetails().getRefreshToken();
const endpoint = `/oauth/token?grant_type=refresh_token&refresh_token=${t}`;
const response = await this.client.post(endpoint, {
headers: {
Authorization: `Basic ${this.config.basicKey}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
});
this.storage.remove(key);
this._saveAuthenticationDetails(response.data);
} catch (error) {
this.storage.remove(key);
if (error.response) {
const errorCode = error.response.status;
throw new AuthenticationError(error.message, errorCode);
}
const errorCode = 410;
throw new AuthenticationError(this._errors.get(errorCode), errorCode);
}
}
/**
* Store authentication details to localStorage
*
* @param authenticationData
* @private
*/
_saveAuthenticationDetails(authenticationData: AuthTokenType) {
const key = this._storageEntries.authenticationToken;
this.storage.save(key, authenticationData, authenticationData.expires_in);
}
/**
* Get authentication details from localStorage
*
* @returns {Authentication}
* @throws AuthenticationError if authentication details are not available
*/
getAuthenticationDetails(): Authentication {
const key = this._storageEntries.authenticationToken;
return new Authentication(this.storage.get(key));
}
}
import nock from 'nock';
import AuthenticationService from '../authenticationService';
import loginTokenSuccessData from '../__fixture__/loginTokenSuccess.json';
import loginTokenRefresh from '../__fixture__/loginTokenRefresh.json';
import loginTokenFailData from '../__fixture__/loginTokenFail.json';
describe('authenticationService', () => {
const nockInstance = nock('http://server.com').defaultReplyHeaders({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Authorization',
});
const authClient = new AuthenticationService({
baseURL: 'http://server.com',
basicKey: '123',
});
it('should accept custom timeout', () => {
const localClient = new AuthenticationService({
baseURL: 'http://server.com',
basicKey: '123',
timeout: 2500,
});
expect(localClient.config.timeout).toBe(2500);
expect(localClient.client.defaults.timeout).toBe(2500);
});
it('should use default baseURL', () => {
const localClient = new AuthenticationService({
basicKey: '123',
});
expect(localClient.config.baseURL).not.toBeDefined();
expect(localClient.client.defaults.baseURL).toBe('http://localhost:8080');
});
describe('login', () => {
beforeEach(() => {
nockInstance.options('/oauth/token?grant_type=password').reply(204);
});
it('should be able to login to the system', async () => {
nockInstance
.post(
'/oauth/token?grant_type=password',
{
username: 'login',
password: '481200',
},
{
headers: {
Authorization: 'Basic 123',
},
},
)
.reply(200, loginTokenSuccessData);
const data = await authClient.login('login', '481200');
expect(data).not.toBeNull();
expect(data.access_token).toBe(loginTokenSuccessData[ 'access_token' ]);
});
it('should store token returned from request', async () => {
nockInstance
.post(
'/oauth/token?grant_type=password',
{
username: 'login',
password: '481200',
},
{
headers: {
Authorization: 'Basic 123',
},
},
)
.reply(200, loginTokenSuccessData);
await authClient.login('login', '481200');
const token = authClient.getAuthenticationDetails().getAccessToken();
expect(token).toBe(loginTokenSuccessData[ 'access_token' ]);
});
it('should fail if basicKey is not present', async () => {
try {
new AuthenticationService({
baseURL: 'http://server.com',
});
} catch (e) {
expect(e.message).toBe('[601] - authentication.missing-basic-key');
}
});
it('should fail if any data is invalid', async () => {
nockInstance
.post(
'/oauth/token?grant_type=password',
{
username: 'login',
password: '481200',
},
{
headers: {
Authorization: 'Basic 123',
},
},
)
.reply(400, loginTokenFailData);
try {
await authClient.login('login', '481200');
} catch (e) {
expect(e.errorCode).toBe(400);
expect(e.message).toContain('Usuário não existe');
}
});
it('should fail if service is unavailable', async () => {
nockInstance
.post(
'/oauth/token?grant_type=password',
{
username: 'login',
password: '481200',
},
{
headers: {
Authorization: 'Basic 123',
},
},
)
.reply(503);
try {
await authClient.login('login', '481200');
} catch (e) {
expect(e.errorCode).toBe(503);
expect(e.message).toContain('Request failed with status code 503');
}
});
});
describe('logout', () => {
const localStorage = authClient.storage;
it('should logout a user and delete credentials', async () => {
nockInstance
.delete(
'/oauth/token?grant_type=password',
{
headers: {
Authorization: 'Bearer 123',
},
},
)
.reply(200);
localStorage.save('authenticationToken', loginTokenSuccessData);
await authClient.logout();
expect(localStorage.get('authenticationToken')).not.toBeDefined();
});
it('should succeed if logout request fail', async () => {
nockInstance
.delete(
'/oauth/token?grant_type=password',
{
headers: {
Authorization: 'Bearer 123',
},
},
)
.reply(503);
localStorage.save('authenticationToken', loginTokenSuccessData);
await authClient.logout();
expect(localStorage.get('authenticationToken')).not.toBeDefined();
});
it('should ignore logout request if auth details are not stored', async () => {
nockInstance
.delete(
'/oauth/token?grant_type=password',
{
headers: {
Authorization: 'Bearer 123',
},
},
)
.reply(503);
await authClient.logout();
expect(localStorage.get('authenticationToken')).not.toBeDefined();
});
});
describe('refreshToken', () => {
beforeEach(() => authClient.storage.remove('authenticationToken'));
it('should refresh token and get new credentials', async () => {
authClient.storage.save('authenticationToken', loginTokenSuccessData);
const t = loginTokenSuccessData[ 'refresh_token' ];
nockInstance
.post(`/oauth/token?grant_type=refresh_token&refresh_token=${t}`)
.reply(200, loginTokenRefresh);
await authClient.refreshToken();
const newToken = authClient.storage.get('authenticationToken');
expect(newToken).toEqual(loginTokenRefresh);
expect(newToken).not.toEqual(loginTokenSuccessData);
});
it('should remove tokens if the request fail', async () => {
authClient.storage.save('authenticationToken', loginTokenSuccessData);
const t = loginTokenSuccessData[ 'refresh_token' ];
nockInstance
.post(`/oauth/token?grant_type=refresh_token&refresh_token=${t}`)
.reply(500);
try {
await authClient.refreshToken();
} catch (e) {
const newToken = authClient.storage.get('authenticationToken');
expect(newToken).not.toBeDefined();
expect(e.errorCode).toBe(500);
}
});
it('should fail if old tokens are not present', async () => {
nockInstance
.post('/oauth/token?grant_type=refresh_token&refresh_token=123')
.reply(500);
try {
await authClient.refreshToken();
} catch (e) {
const newToken = authClient.storage.get('authenticationToken');
expect(newToken).not.toBeDefined();
expect(e.errorCode).toBe(410);
}
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment