npm package: @notiz/nest-auth-formly
Description: Nest Auth UI with TailwindCSS and Formly for Angular
Author: notiz
Latest version: 0.2.0

# NestAuthFormly

[![npm version](https://badge.fury.io/js/@notiz%2Fnest-auth-formly.svg)](https://www.npmjs.com/package/@notiz/nest-auth-formly)

Works together with [nest-auth](https://github.com/notiz-dev/nest-auth)

## Installation

Requires Tailwind, Tailwind Forms Plugin, Formly and Tailwind-Formly

```bash
# select forms plugin
ng add ngx-tailwind

# alternative install via
npm install -D @tailwindcss/forms

ng add @ngx-formly/schematics
# or
npm i @ngx-formly/core

npm i @notiz/formly-tailwindcss
```

Your `AppModule` should look like this and import `HttpClientModule`

```ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ReactiveFormsModule } from '@angular/forms';
import { FormlyModule } from '@ngx-formly/core';
import { FormlyTailwindcssModule } from '@notiz/formly-tailwindcss';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule,
    FormlyModule.forRoot({
      extras: { lazyRender: true },
    }),
    FormlyTailwindcssModule,
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

Now install ... NOT YET AVAILABLE

Nest Application works great with code generators such as [ng-openapi-gen](https://github.com/cyclosproject/ng-openapi-gen) for Rest or [graphql-code-generator](https://github.com/dotansimha/graphql-code-generator) for GraphQL to create Angular services for your endpoints.

## Usage

### NestAuthFormlyModule

Import `NestAuthFormlyModule` which includes `JwtInterceptor`, read more about [Interceptor](#Interceptor).

```ts
import { environment } from '@env/environment';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { FormlyModule } from '@ngx-formly/core';
import { FormlyTailwindcssModule } from '@notiz/formly-tailwindcss';

import { NestAuthFormlyModule } from '@notiz/nest-auth-formly';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule,
    FormlyModule.forRoot({
      extras: { lazyRender: true },
      validationMessages: [{ name: 'required', message: 'Is required' }],
    }),
    FormlyTailwindcssModule,
    HttpClientModule,
    NestAuthFormlyModule.forRoot({
      httpInterceptor: {
        // attaches access token only to the allowed endpoints
        allowedList: [
          // `${environment.baseUrl}/users/me`,
          // `${environment.baseUrl}/graphql`,
          `${environment.baseUrl}/*`,
        ],
      },
    }),
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

### Social Login

To add social login buttons via formly import `SocialLoginModule` to register formly type `socialLogin`.

```ts
import { environment } from '@env/environment';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { FormlyModule } from '@ngx-formly/core';
import { FormlyTailwindcssModule } from '@notiz/formly-tailwindcss';

import { NestAuthFormlyModule } from '@notiz/nest-auth-formly';
import { SocialLoginModule } from '@notiz/nest-auth-formly/social';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule,
    FormlyModule.forRoot({
      extras: { lazyRender: true },
      validationMessages: [{ name: 'required', message: 'Is required' }],
    }),
    FormlyTailwindcssModule,
    HttpClientModule,
    NestAuthFormlyModule,
    SocialLoginModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

### Interceptor

`JwtInterceptor` adds the access token, if available, to the request as Authorization Bearer token.

Import `NestAuthFormlyModule` or add `JwtInterceptor` as a provider

```ts
import { environment } from '@env/environment';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ReactiveFormsModule } from '@angular/forms';
import { FormlyModule } from '@ngx-formly/core';
import { FormlyTailwindcssModule } from '@notiz/formly-tailwindcss';
import { JwtInterceptor } from '@notiz/nest-auth-formly';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule,
    FormlyModule.forRoot({
      extras: { lazyRender: true },
      validationMessages: [{ name: 'required', message: 'Is required' }],
    }),
    FormlyTailwindcssModule,
    HttpClientModule,
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

### Guards

`AuthGuard` protects your routes from not authenticated users.

```ts
import { AuthGuard } from '@notiz/nest-auth-formly';

const routes: Routes = [
  { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
  {
    path: 'login',
    loadChildren: () =>
      import('./auth/login/login.module').then((m) => m.LoginModule),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./dashboard/dashboard.module').then((m) => m.DashboardModule),
    canActivate: [AuthGuard],
  },
];
```

#### Customize Guard behavior

Customize the behavior of `AuthGuard` by passing an rxJS pipe through the route data's `authGuardPipe` key.

The following pre-built pipes are available:

| Pipes                    |  Description                                                   |
| ------------------------ | -------------------------------------------------------------- |
| `loggedIn`               | The default pipe, rejects if the user is not authenticated     |
| `hasRole`                | Rejects authenticated users without specific role              |
| `hasRoleOf`              | Rejects authenticated users without one of the specified roles |
| `emailVerified`          | Rejects authenticated users without verified email             |
| `redirectLoggedInTo`     | Redirect authenticated users to a different route              |
| `redirectUnauthorizedTo` | Redirect unauthenticated users to a different route            |

Example use:

```ts
import {
  AuthGuard,
  loggedIn,
  redirectLoggedInTo,
  redirectUnauthorizedTo,
} from '@notiz/nest-auth-formly';

const routes: Routes = [
  { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
  {
    path: 'login',
    loadChildren: () =>
      import('./auth/login/login.module').then((m) => m.LoginModule),
    canActivate: [AuthGuard],
    data: { authGuardPipe: () => redirectLoggedInTo(['']) },
  },
  {
    path: 'profile',
    loadChildren: () =>
      import('./profile/profile.module').then((m) => m.ProfileModule),
    canActivate: [AuthGuard],
    data: { authGuardPipe: () => loggedIn }, // Default behavior of AuthGuard
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./dashboard/dashboard.module').then((m) => m.DashboardModule),
    canActivate: [AuthGuard],
    data: { authGuardPipe: () => redirectUnauthorizedTo(['login']) },
  },
  {
    path: 'admin',
    loadChildren: () =>
      import('./admin/admin.module').then((m) => m.AdminModule),
    canActivate: [AuthGuard],
    data: { authGuardPipe: () => hasRole('ADMIN') },
  },
];
```

Use `canActivate` helper and spread syntax to make your routes more readable:

```ts
import {
  AuthGuard,
  canActivate
  loggedIn,
  redirectLoggedInTo,
  redirectUnauthorizedTo,
} from '@notiz/nest-auth-formly';

const routes: Routes = [
  { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
  {
    path: 'login',
    loadChildren: () =>
      import('./auth/login/login.module').then((m) => m.LoginModule),
    ...canActivate(() => redirectLoggedInTo(['']))
  },
  {
    path: 'profile',
    loadChildren: () =>
      import('./profile/profile.module').then((m) => m.ProfileModule),
    ...canActivate(() => loggedIn([''])) // Default behavior of AuthGuard
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./dashboard/dashboard.module').then((m) => m.DashboardModule),
    ...canActivate(() => redirectUnauthorizedTo(['login'])),
  },
  {
    path: 'admin',
    loadChildren: () =>
      import('./admin/admin.module').then((m) => m.AdminModule),
    ...canActivate(() => hasRole('ADMIN')),
  },
];
```

#### Compose your own pipes

```ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { canActivate, hasRole } from '@notiz/nest-auth-formly';
import { pipe } from 'rxjs';

const adminOnly = pipe(hasRole('ADMIN'));

const routes: Routes = [
  ...{
    path: 'admin',
    loadChildren: () =>
      import('./admin/admin.module').then((m) => m.AdminModule),
    ...canActivate(() => adminOnly),
  },
];
```

#### Using router state

```ts
const onlyAllowSelf = (next) =>
  map((user) => !!user && next.params.userId === user.id);
```

## Development

Start the demo of [nest-auth](https://github.com/notiz-dev/nest-auth)

```bash
npm i

ng s

# generate rest
npm run codegen:rest
# generate graphql
npm run codegen:graphql
```