Skip to content
This repository has been archived by the owner on Jan 4, 2019. It is now read-only.

Commit

Permalink
Merge branch 'release/3.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
gallayl committed Nov 24, 2017
2 parents 8d7d7d5 + f0c475f commit 8e55de8
Show file tree
Hide file tree
Showing 90 changed files with 7,108 additions and 7,914 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ cache:
notifications:
email: false
node_js:
- '9'
- '8'
- '7'
- '6'
Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sn-client-js",
"version": "2.5.0",
"version": "3.0.0",
"description": "A JavaScript client for Sense/Net ECM that makes it easy to use the REST API of the Content Repository.",
"main": "dist/src/SN.js",
"files": [
Expand All @@ -11,7 +11,7 @@
"coverage"
],
"scripts": {
"lint": "./node_modules/.bin/tslint --project tsconfig.json --type-check",
"lint": "./node_modules/.bin/tslint --project tsconfig.json",
"clean": "rimraf dist",
"precommit": "npm run lint",
"commit": "git-cz",
Expand Down Expand Up @@ -59,9 +59,9 @@
},
"homepage": "https://sensenet.com",
"dependencies": {
"@reactivex/rxjs": "^5.4.2",
"sensenet-kfi-cz-conventional-changelog": "^1.0.0",
"nyc": "^11.0.2"
"nyc": "^11.0.2",
"rxjs": "^5.5.2",
"sensenet-kfi-cz-conventional-changelog": "^1.0.0"
},
"devDependencies": {
"@types/app-root-path": "1.2.4",
Expand All @@ -74,11 +74,11 @@
"mocha": "4.0.1",
"mocha-typescript": "^1.0.23",
"rimraf": "^2.6.1",
"tslint": "^5.4.3",
"tslint-consistent-codestyle": "^1.8.0",
"tslint": "^5.8.0",
"tslint-consistent-codestyle": "^1.9.0",
"typedoc": "^0.9.0",
"typedoc-plugin-external-module-name": "^1.0.9",
"typescript": "2.5.3"
"typescript": "^2.6.1"
},
"config": {
"commitizen": {
Expand Down
13 changes: 9 additions & 4 deletions src/Authentication/IAuthenticationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
* @module Authentication
*/ /** */

import { Observable } from '@reactivex/rxjs';
import { LoginState } from './';
import { Observable } from 'rxjs/Observable';
import { IOauthProvider, LoginResponse, LoginState } from './';

/**
* Interface that describes how injectable Authentication Services should work
*/
export interface IAuthenticationService {
/**
* This observable is a public API for subscribing the current state and it's changes. Recommended to work with a private BehaviorSubject in the backgroud.
* This observable is a public API for subscribing the current state and it's changes.
* Recommended to work with a private BehaviorSubject in the backgroud.
*/
readonly State: Observable<LoginState>;

Expand Down Expand Up @@ -41,4 +42,8 @@ export interface IAuthenticationService {

CurrentUser: string;

}
HandleAuthenticationResponse(response: LoginResponse): boolean;
SetOauthProvider<T extends IOauthProvider>(provider: T);
GetOauthProvider<T extends IOauthProvider>(providerType: {new(...args): T}): T;

}
18 changes: 18 additions & 0 deletions src/Authentication/IOauthProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @module Authentication
*/ /** */

/**
* Interface that represents a basic structure for an additional OAuth Provider
*/
export interface IOauthProvider {
/**
* Method that retrieves the token info
*/
GetToken(): Promise<string>;

/**
* Method that handles the user login. Should be responsible for updating the Authentication State as well.
*/
Login(token: string): Promise<any>;
}
2 changes: 1 addition & 1 deletion src/Authentication/ITokenPayload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ export interface ITokenPayload {
* name: identifies the name of the user whom the token was issued to
*/
name: string;
}
}
123 changes: 75 additions & 48 deletions src/Authentication/JwtService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
* @module Authentication
*/ /** */

import { LoginState, LoginResponse, RefreshResponse, Token, TokenStore, IAuthenticationService, TokenPersist } from './';
import { Subject, BehaviorSubject, Observable } from '@reactivex/rxjs';
import { BaseHttpProvider } from '../HttpProviders/BaseHttpProvider';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { BaseRepository } from '../Repository/BaseRepository';
import { ODataHelper } from '../SN';
import { IAuthenticationService, IOauthProvider, LoginResponse, LoginState, RefreshResponse,
Token, TokenPersist, TokenStore } from './';

/**
* This service class manages the JWT authentication, the session and the current login state.
Expand All @@ -14,42 +17,75 @@ export class JwtService implements IAuthenticationService {

private readonly _visitorName: string = 'BuiltIn\\Visitor';

private _oauthProviders: Map<{new(...args): IOauthProvider}, IOauthProvider> = new Map();

/**
* Sets a specified OAuth provider
* @param {IOauthProvider} provider The provider instance to be set
* @throws if a provider with the specified type has already been set
*/
public SetOauthProvider<T extends IOauthProvider>(provider: T) {
const providerCtor = provider.constructor as {new(...args)};
if (this._oauthProviders.has(providerCtor)) {
throw Error(`Provider for '${providerCtor.name}' already set`);
}
this._oauthProviders.set(providerCtor, provider);
}

/**
* Gets the specified OAuth provider instance
* @param {<T>} providerType The provider type to be retrieved
* @throws if the provider hasn't been registered
*/
public GetOauthProvider<T extends IOauthProvider>(providerType: {new(...args): T}): T {
if (!this._oauthProviders.has(providerType)) {
throw Error(`OAuth provider not found for '${providerType.name}'`);
}
return this._oauthProviders.get(providerType) as T;
}

/**
* Returns the current user's name as a string. In case of unauthenticated users, it will return 'BuiltIn\Visitor'
*/
public get CurrentUser(): string {
if (this._tokenStore.AccessToken.IsValid() || this._tokenStore.RefreshToken.IsValid()){
if (this._tokenStore.AccessToken.IsValid() || this._tokenStore.RefreshToken.IsValid()) {
return this._tokenStore.AccessToken.Username || this._tokenStore.RefreshToken.Username;
}
return this._visitorName;
};
}
/**
* This subject indicates the current state of the service
* This observable indicates the current state of the service
* @default LoginState.Pending
*/
public get State(): Observable<LoginState>{
public get State(): Observable<LoginState> {
return this._stateSubject.distinctUntilChanged();
}

/**
* Gets the current state of the service
* @default LoginState.Pending
*/
public get CurrentState(): LoginState{
public get CurrentState(): LoginState {
return this._stateSubject.getValue();
}

private readonly _stateSubject: BehaviorSubject<LoginState> = new BehaviorSubject<LoginState>(LoginState.Pending);
/**
* The private subject for tracking the login state
*/
protected readonly _stateSubject: BehaviorSubject<LoginState> = new BehaviorSubject<LoginState>(LoginState.Pending);

/**
* The store for JWT tokens
*/
private _tokenStore: TokenStore = new TokenStore(this._repositoryUrl, this._tokenTemplate, (this.Persist === 'session') ? TokenPersist.Session : TokenPersist.Expiration);

private _tokenStore: TokenStore =
new TokenStore(this._repository.Config.RepositoryUrl, this._repository.Config.JwtTokenKeyTemplate, (this._repository.Config.JwtTokenPersist === 'session') ? TokenPersist.Session : TokenPersist.Expiration);

/**
* Executed before each Ajax call. If the access token has been expired, but the refresh token is still valid, it triggers the token refreshing call
* @returns {Observable<boolean>} An observable with a variable that indicates if there was a refresh triggered.
*/
public CheckForUpdate(): Observable<boolean> {
if (this._tokenStore.AccessToken.IsValid()){
if (this._tokenStore.AccessToken.IsValid()) {
this._stateSubject.next(LoginState.Authenticated);
return Observable.from([false]);
}
Expand All @@ -66,51 +102,44 @@ export class JwtService implements IAuthenticationService {
* @returns {Observable<boolean>} An observable that will be completed with true on a succesfull refresh
*/
private execTokenRefresh() {
let refresh = this._httpProviderRef.Ajax(RefreshResponse, {
const refresh = this._repository.HttpProviderRef.Ajax(RefreshResponse, {
method: 'POST',
url: ODataHelper.joinPaths(this._repositoryUrl, 'sn-token/refresh'),
url: ODataHelper.joinPaths(this._repository.Config.RepositoryUrl, 'sn-token/refresh'),
headers: {
'X-Refresh-Data': this._tokenStore.RefreshToken.toString(),
'X-Authentication-Type': 'Token'
}
'X-Authentication-Type': 'Token',
},
});

refresh.subscribe(response => {
refresh.subscribe((response) => {
this._tokenStore.AccessToken = Token.FromHeadAndPayload(response.access);
this._stateSubject.next(LoginState.Authenticated);
}, err => {
console.warn(`There was an error during token refresh: ${err}`);
}, (err) => {
this._stateSubject.next(LoginState.Unauthenticated);
});

return refresh.map(response => { return true });
return refresh.map((response) => true);
}

/**
* @param {BaseHttpProvider} httpProviderRef The Http Provider to use (e.g. login / logout / session renew requests)
* @param {string} repositoryUrl The URL for the repository
* @param {string} tokenTemplate The template to use when generating token keys in session/local storage or in a cookie. ${siteName} and ${tokenName} will be replaced.
* @param {'session' | 'expiration'} persist Sets up if the tokens should be persisted per session (browser close) or per token expiration (based on the token)
* @param {BaseRepository} _repository the Repository reference for the Authentication. The service will read its configuration and use its HttpProvider
* @constructs JwtService
*/
constructor(private readonly _httpProviderRef: BaseHttpProvider,
private readonly _repositoryUrl: string,
private readonly _tokenTemplate: string,
public readonly Persist: 'session' | 'expiration') {
constructor(protected readonly _repository: BaseRepository) {

this._stateSubject = new BehaviorSubject<LoginState>(LoginState.Pending);

this.State.subscribe((s) => {
if (this._tokenStore.AccessToken.IsValid()){
this._httpProviderRef.SetGlobalHeader('X-Access-Data', this._tokenStore.AccessToken.toString());
if (this._tokenStore.AccessToken.IsValid()) {
this._repository.HttpProviderRef.SetGlobalHeader('X-Access-Data', this._tokenStore.AccessToken.toString());
} else {
this._httpProviderRef.UnsetGlobalHeader('X-Access-Data');
this._repository.HttpProviderRef.UnsetGlobalHeader('X-Access-Data');
}
});
this.CheckForUpdate();
}

private handleAuthenticationResponse(response: LoginResponse): boolean {
public HandleAuthenticationResponse(response: LoginResponse): boolean {
this._tokenStore.AccessToken = Token.FromHeadAndPayload(response.access);
this._tokenStore.RefreshToken = Token.FromHeadAndPayload(response.refresh);
if (this._tokenStore.AccessToken.IsValid()) {
Expand Down Expand Up @@ -142,23 +171,23 @@ export class JwtService implements IAuthenticationService {
* ```
*/
public Login(username: string, password: string) {
let sub = new Subject<boolean>();
const sub = new Subject<boolean>();

this._stateSubject.next(LoginState.Pending);
let authToken: String = new Buffer(`${username}:${password}`).toString('base64');
const authToken: string = new Buffer(`${username}:${password}`).toString('base64');

this._httpProviderRef.Ajax(LoginResponse, {
this._repository.HttpProviderRef.Ajax(LoginResponse, {
method: 'POST',
url: ODataHelper.joinPaths(this._repositoryUrl, 'sn-token/login'),
url: ODataHelper.joinPaths(this._repository.Config.RepositoryUrl, 'sn-token/login'),
headers: {
'X-Authentication-Type': 'Token',
'Authorization': `Basic ${authToken}`
}
'Authorization': `Basic ${authToken}`,
},
})
.subscribe(r => {
let result = this.handleAuthenticationResponse(r);
.subscribe((r) => {
const result = this.HandleAuthenticationResponse(r);
sub.next(result);
}, err => {
}, (err) => {
this._stateSubject.next(LoginState.Unauthenticated);
sub.next(false);
});
Expand All @@ -167,20 +196,18 @@ export class JwtService implements IAuthenticationService {
}

/**
* Logs out the current user, sets the tokens to 'empty'
* ```
* service.Logout();
* ```
* Logs out the current user, sets the tokens to 'empty' and sends a Logout request to invalidate all Http only cookies
* @returns {Observable<boolean>} An Observable that will be updated with the logout response
*/
public Logout(): Observable<boolean> {
this._tokenStore.AccessToken = Token.CreateEmpty();
this._tokenStore.RefreshToken = Token.CreateEmpty();
this._stateSubject.next(LoginState.Unauthenticated);

return this._httpProviderRef.Ajax(LoginResponse, {
return this._repository.HttpProviderRef.Ajax(LoginResponse, {
method: 'POST',
url: ODataHelper.joinPaths(this._repositoryUrl, 'sn-token/logout'),
url: ODataHelper.joinPaths(this._repository.Config.RepositoryUrl, 'sn-token/logout'),
}).map(() => true);

}
}
}
10 changes: 6 additions & 4 deletions src/Authentication/LoginResponse.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* @module Authentication
*/ /** */
*/ /** */

// tslint:disable:naming-convention

/**
* This class represents a plain response body that is returned from Sense/NET ECM in case of a succesfully login.
Expand All @@ -9,10 +11,10 @@ export class LoginResponse {
/**
* The Access Token head and payload in a Base64 encoded format
*/
access: string;
public access: string;

/**
* The Refresh Token head and payload in a Base64 encoded format
*/
refresh: string;
}
public refresh: string;
}
8 changes: 4 additions & 4 deletions src/Authentication/LoginState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ export enum LoginState {
/**
* There is a request (login or token refresh) in progress
*/
Pending,
Pending = 'Pending',
/**
* The user is not authenticated
*/
Unauthenticated,
Unauthenticated = 'Unauthenticated',
/**
* The user is authenticated and has a valid access token
*/
Authenticated
}
Authenticated = 'Authenticated',
}
6 changes: 4 additions & 2 deletions src/Authentication/RefreshResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
* @module Authentication
*/ /** */

// tslint:disable:naming-convention

/**
* This class represents a plain response body that is returned from Sense/NET ECM in case of a succesfully login.
*/
export class RefreshResponse {
/**
* The Access Token head and payload in a Base64 encoded format
*/
access: string;
}
public access: string;
}
Loading

0 comments on commit 8e55de8

Please sign in to comment.