Skip to content

Latest commit

 

History

History
523 lines (411 loc) · 14.6 KB

README.md

File metadata and controls

523 lines (411 loc) · 14.6 KB

Ronas IT Angular Common

Common Angular services for communicating with backend, authentication and user managing.

Live demo

In progress...

About the library

Ronas IT Angular Common working with cookies. One of the main advantages of this approach is that cookies can be HTTP-only. It makes them read-protected on the client side, that improves safety against any Cross-site scripting (XSS) attacks. Cookie-based authentication allows using this services in Server-Side Rendering (SSR) applications.

Getting Started

Installation

Install Ronas IT Angular Common:

npm i @ronas-it/angular-common --save

Usage

ApiModule

  1. Add ApiModule to AppModule imports:
import { ApiModule } from '@ronas-it/angular-common';
import { configuration } from '@configuration';

@NgModule({
  imports: [
    ApiModule.forRoot({
      apiUrl: configuration.api.url
    }),
    ...
  ],
  ...
})
export class AppModule { }
  1. Inject ApiService and use it:
import { ApiService } from '@ronas-it/angular-common';
import { Injectable } from '@angular/core';

@Injectable()
export class ProductService {
  constructor(
    private apiService: ApiService
  ) { }

  public delete(id: number): Observable<void> {
    return this.apiService.delete(`/products/${id}`);
  }

  ...
}

CookieModule

  1. Add CookieModule to AppModule imports:
import { CookieModule } from '@ronas-it/angular-common';

@NgModule({
  imports: [
    CookieModule.forRoot({
      defaultOptions: { path: '/', /* other cookie options ... */ }
    }),
    ...
  ],
  ...
})
export class AppModule { }
  1. Inject CookieService and use it:
import { BehaviorSubject, Subject } from 'rxjs';
import { CookieService } from '@ronas-it/angular-common';
import { Injectable } from '@angular/core';

@Injectable()
export class CookiePopupFacade {
  private isCookiesAccepted$: Subject<boolean>;

  constructor(
    private cookieService: CookieService
  ) { 
    this.isCookiesAccepted$ = new BehaviorSubject(this.cookieService.get('isCookiesAccepted') === 'true');
  }

  public acceptCookies(): void {
    this.isCookiesAccepted$.next(true);

    this.cookieService.put('isCookiesAccepted', 'true', { maxAge: 4e10 });
  }

  ...
}
  1. (SSR Only) Add providers for REQUEST and RESPONSE injection tokens from @nguniversal/express-engine/tokens in server.ts:
server.get('*', (req, res) => {
  res.render(indexHtml, {
    req,
    res,
    providers: [
      {
        provide: APP_BASE_HREF,
        useValue: req.baseUrl
      },
      {
        provide: REQUEST,
        useValue: req
      },
      {
        provide: RESPONSE,
        useValue: res
      }
    ]
  });
});
  1. (SSR Only) Set requestToken and responseToken parameters in the CookieModule config:
import { CookieModule } from '@ronas-it/angular-common';
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';

@NgModule({
  imports: [
    CookieModule.forRoot({
      defaultOptions: { /* ... */ },
      requestToken: REQUEST,
      responseToken: RESPONSE
    }),
    ...
  ],
  ...
})
export class AppModule { }

UserModule

Note: This module depends on ApiModule and AuthModule. Please make sure to install them prior to installing this module.

  1. Create a User model and extend it from AbstractUser:
import { AbstractUser } from '@ronas-it/angular-common';
import { Expose } from 'class-transformer';

export class User extends AbstractUser {
  @Expose({ groups: ['main'] })
  public id: number;

  @Expose({ groups: ['main'] })
  public name: string;

  @Expose({ groups: ['main'] })
  public email: string;
}
  1. Create a UserService and extend it from CommonUserService:
import { UserService as CommonUserService } from '@ronas-it/angular-common';

@Injectable()
export class UserService extends CommonUserService<User> {
  /* Define custom methods or override existing methods here. */
}
  1. Create a UserModule and add CommonUserModule to imports:
import { NgModule } from '@angular/core';
import { User } from './models';
import { UserModule as CommonUserModule } from '@ronas-it/angular-common';
import { UserService } from './user.service';

@NgModule({
  imports: [
    CommonUserModule.forRoot({
      userModel: User,
      userService: UserService
    }),
    ...
  ],
  ...
})
export class UserModule { }
  1. Inject UserService and use it:
import { Action } from '@ngrx/store';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { AuthService } from '@shared/auth';
import { catchError, filter, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators';
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { User } from '../models';
import { userActions } from './actions';
import { UserService } from '../user.service';

@Injectable()
export class UserEffects {
  public refreshProfile$: Observable<Action> = createEffect(
    () => this.actions$.pipe(
      ofType(userActions.refreshProfile),
      withLatestFrom(this.authService.isAuthenticated$),
      filter(([_, isAuthenticated]) => isAuthenticated),
      switchMap(() => {
        return this.userService.refreshProfile()
          .pipe(
            mergeMap((user: User) => [
              userActions.updateProfile({ profile: user }),
              userActions.refreshProfileSuccess()
            ]),
            catchError((response: HttpErrorResponse) => of(userActions.refreshProfileFailure({ response })))
          );
      })
    )
  );

  constructor(
    private actions$: Actions,
    private authService: AuthService,
    private userService: UserService
  ) { }
}

AuthModule

Note: This module depends on ApiModule, CookieModule and UserModule. Please make sure to install them prior to installing this module.

  1. Create an AuthService and extend it from CommonAuthService:
import { AuthService as CommonAuthService } from '@ronas-it/angular-common';
import { Injectable } from '@angular/core';
import { User } from '@shared/user';

@Injectable()
export class AuthService extends CommonAuthService<User> {
  /* Define custom methods or override existing methods here. */
}
  1. Create an AuthModule and add CommonAuthModule to imports:
import { AuthModule as CommonAuthModule } from '@ronas-it/angular-common';
import { AuthService } from './auth.service';
import { configuration } from '@configuration';
import { NgModule } from '@angular/core';

@NgModule({
  imports: [
    CommonAuthModule.forRoot({
      unauthorizedRoutes: configuration.api.unauthorized_routes,
      authService: AuthService,

      // Optionally, you can pass `unauthenticatedRoute` parameter that
      // specifies the route to redirect to after logout or when a user is
      // not authenticated to view some page. By default it is set to `/login`.
      unauthenticatedRoute: '/'
    }),
    ...
  ],
  ...
})
export class AuthModule { }
  1. Inject AuthService and use it:
import { Action } from '@ngrx/store';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { authActions } from './actions';
import { AuthService } from '../auth.service';
import { catchError, exhaustMap, map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable()
export class AuthEffects {
  public authorize$: Observable<Action> = createEffect(
    () => this.actions$.pipe(
      ofType(authActions.authorize),
      exhaustMap((action) => {
        return this.authService
          .authorize(action.credentials)
          .pipe(
            map((response) => authActions.authSuccess({ response })),
            catchError((response) => of(authActions.authFailure({ response })))
          );
      })
    )
  );

  constructor(
    private actions$: Actions,
    private authService: AuthService
  ) { }
}

API

ApiModule

Config

ApiModule.forRoot(config: ApiConfig)
ApiConfig
Name Type Required Description
apiUrl string Yes Endpoint that allows you to access an API
trailingSlash boolean No The need for trailing slash (https://api.your-service.com/login/ for example)
enableArrayKeys boolean No Enabling array keys for http params
fileKeys Array<string> No List of the file keys for http params

ApiService

Field Type
apiUrl string
trailingSlash string
fileKeys Array<string>
Method Arguments Return type
get<T> endpoint: string, params: any, options: object Observable<T>
post<T> endpoint: string, data: any, options: object Observable<T>
put<T> endpoint: string, data: any, options: object Observable<T>
delete<T> endpoint: string, params: any, options: object Observable<T>

CookieModule

Config

CookieModule.forRoot(config: CookieConfig)
CookieConfig
Name Type Required Description
defaultOptions CookieOptions No Cookie options that will be used if not specified in the put method
requestToken InjectionToken<Request> No Request injection token from @nguniversal/express-engine/tokens for cookies support in SSR
responseToken InjectionToken<Response> No Response injection token from @nguniversal/express-engine/tokens for cookies support in SSR
CookieOptions
Name Type
maxAge number
expires Date
path string
domain string
secure boolean
sameSite boolean | 'lax' | 'strict' | 'none'

CookieService<TKey extends string = string>

Field Type
cookieString string
Method Arguments Return type
get key: TKey string | null
getObject key: TKey object | null
getAll Record<string, string>
hasKey key: TKey boolean
put key: TKey, value: string, options?: CookieOptions void
putObject key: TKey, value: object, options?: CookieOptions void
remove key: TKey, options?: CookieOptions void
removeAll options?: CookieOptions void

AuthModule

Config

CommonAuthModule.forRoot(config: AuthConfig)
AuthConfig
Name Type Required Description
unauthorizedRoutes Array<string> Yes Routes that don't need authorization (public routes, e.g. login, registration and forgot password pages)
authService new (...args: Array<any>) => any Yes Service that will be used in your app
unauthenticatedRoute string No Route to redirect to after logout or when a user is not authenticated to view some page. By default it is set to /login
disableRedirectAfterUnauthorize boolean No Whether to redirect to unauthenticatedRoute after logout or when a user is not authenticated to view some page. By default it is set to false
authenticatedRoute string No Route to redirect after successful login
loginEndpoint string No Endpoint for login, e.g. /api/token
refreshTokenEndpoint string No Endpoint for refreshing token, e.g. /api/token/refresh
refreshTokenEndpointMethod 'get' | 'post' No HTTP Method that will be used for calling endpoint to refresh token
isAuthenticatedField string No Field for cookie
rememberField string No Field for cookie
cookiesExpirationDays number No Expiration for authentication cookies when call authorize with remember flag set to true. By default it is set to 365

AuthService<User extends AbstractUser>

Static constant Type
DEFAULT_LOGIN_ENDPOINT string
DEFAULT_UNAUTHENTICATED_ROUTE string
DEFAULT_IS_AUTHENTICATED_FIELD string
DEFAULT_REFRESH_TOKEN_ENDPOINT string
DEFAULT_REMEMBER_FIELD string
DEFAULT_COOKIES_EXPIRATION_DAYS number
Field Type
isTokenRefreshing$ Observable<boolean>
isAuthenticated$ Observable<boolean>
cookiesExpiresDate Date
Method Arguments Return type
authorize<T> credentials: AuthCredentials & T, remember: boolean Observable<AuthResponse<User>>
manuallyAuthorize authResponse: object, remember: boolean = true Observable<AuthResponse<User>>
unauthorize void
refreshToken Observable<HttpResponse<void>>
setIsAuthenticated remember: boolean void
resetIsAuthenticated void
resetRemember void

AuthCredentials

Field Type Required
email string No
password string Yes

AuthResponse<User extends AbstractUser>

Field Type Required
user User No

UserModule

Config

UserModule.forRoot(config: UserConfig)
UserConfig
Name Type Required Description
userModel new (user: any) => any Yes Model (class) for user
userService new (...args: Array<any>) => any Yes Your UserService implementation
profileRelations Array<string> No Relations for getting profile request. For example: /profile?with[]=company&with[]=clients
profileRelationsKey string No with by default

UserService<User extends AbstractUser>

Field Type
profile$ Observable<User>
Method Arguments Return type
refreshProfile Observable<User>
loadProfile Observable<User>
updateProfile user: User Observable<void>
updatePassword userPasswords: UserPasswords Observable<void>
setProfile user: User void
patchProfile user: Partial<User> void
resetRemember void
resetProfile void
userToPlain user: User, options?: ClassTransformOptions Object
plainToUser plain: object, options?: ClassTransformOptions User

AbstractUser

Field Type Required
id number | string Yes

Contributing

Contributions to Ronas IT Angular Common are welcome. The contribution guide can be found in the Contributing guide.

License

Ronas IT Angular Common is open-sourced software licensed under the MIT license.