Complete built-in auth system for Rockets: signup, login, password recovery, OTP, invitations, roles, admin user CRUD — wired as a single
defineRocketsAuth()integration.
Status: alpha (1.0.0-alpha.7, currently private in package.json — flipped to public on the first 1.0-track release). API on the defineRocketsAuth() surface is stable; the OAuth submodule is parked pending upstream v8 ports (see Known limitations).
@bitwild/rockets-auth is what you reach for when your application owns its users. It is the alternative to @bitwild/rockets for the case where you do not delegate authentication to an external IdP.
It composes the v8 line of @concepta/nestjs-* identity motors (user, password, otp, role, invitation, federated, email, event, plus authentication) into a single configuration shape and exposes them as an AuthBootstrap for RocketsModule.forRoot({ auth: ... }) from @bitwild/rockets. It does not replace repository/CRUD/hook motors — those still come from core + @bitwild/rockets-repository / crud / common.
- HTTP routes (mounted by the bundle):
POST /token/password— login.POST /token/refresh— refresh.PATCH /me(password change) and the rest of/mefrom@bitwild/rockets.POST /otp,PATCH /otp— OTP issue / verify.POST /signup— user signup (wired throughuserCrud).- Admin:
/admin/users,/admin/users/:userId/roles,/admin/invitations(+ accept / revoke / reattempt). /invitation-acceptancefor invited users.
- Provider:
RocketsJwtAuthAdapter— Rockets-specAuthAdapterInterfacethat validates the JWT issued by/token/passwordand produces anAuthorizedUserwithuserRoles. - Access control re-exports from
@concepta/nestjs-access-controlso app code single-sources from this package. - Customisation seams: per-controller decorator extras (
controller.classDecorators,controller.routes[*].decorators), abstract handler classes for every admin operation, port overrides for every cross-module command/query.
- You want a complete user system out of the box (signup, login, OTP, password recovery, roles, invitations, admin endpoints) and you don't want to glue seven modules together yourself.
- You will deploy in environments where the application owns the identity store.
- Users live in an external IdP (Firebase, Auth0, Okta, custom JWT) → use
@bitwild/rockets+ the matching adapter. - You only need login + a custom user table without OTP / invitations / admin → drop to
@bitwild/rocketsand write a small JWT adapter yourself.
yarn add @bitwild/rockets-auth @bitwild/rockets @bitwild/rockets-core \
@bitwild/rockets-common @bitwild/rockets-repository @bitwild/rockets-crud \
@nestjs/common @nestjs/core @nestjs/cqrs @nestjs/swagger @nestjs/jwt @nestjs/passport \
class-transformer class-validator reflect-metadata rxjsBring the upstream @concepta/nestjs-* packages and a repository adapter your app supports (e.g. @concepta/nestjs-repository-typeorm + typeorm).
import { Module } from '@nestjs/common';
import { EventModule } from '@concepta/nestjs-event';
import { RocketsModule } from '@bitwild/rockets';
import { defineRocketsAuth, buildRocketsAuthResources } from '@bitwild/rockets-auth';
import { defineTypeOrmRepository } from './repository/define-typeorm-repository';
import {
UserEntity, UserCredentialEntity, UserOtpEntity,
RoleEntity, UserRoleEntity, FederatedEntity, InvitationEntity,
UserDto, UserCreateDto, SampleUserUpdateDto,
} from './user';
import { UserMetadataEntity, UserMetadataCreateDto, UserMetadataUpdateDto }
from './user/metadata';
import { RoleDto, RoleCreateDto, RoleUpdateDto } from './role';
// One bootstrap instance — same reference on `repository` and `persistence.module`.
const repo = defineTypeOrmRepository({
type: 'sqlite',
database: ':memory:',
synchronize: true,
dropSchema: true,
});
const rocketsAuthInput = {
persistence: {
module: repo,
entities: {
user: UserEntity,
userCredentials: UserCredentialEntity,
userOtp: UserOtpEntity,
role: RoleEntity,
userRole: UserRoleEntity,
federatedIdentity: FederatedEntity,
},
},
invitationEntity: InvitationEntity,
userMetadata: {
entity: UserMetadataEntity,
createDto: UserMetadataCreateDto,
updateDto: UserMetadataUpdateDto,
},
userCrud: { model: UserDto, dto: { createOne: UserCreateDto, updateOne: SampleUserUpdateDto } },
roleCrud: { model: RoleDto, dto: { createOne: RoleCreateDto, updateOne: RoleUpdateDto } },
useFactory: () => ({
services: {
mailerService: {
sendMail: async (opts) => { /* wire your real SMTP / SES adapter */ },
},
},
authentication: {
ports: {
recoveryNotification: {
sendRecoverLoginNotificationCommand: SendRecoverLoginCmd,
sendRecoverPasswordNotificationCommand: SendRecoverPasswordCmd,
sendPasswordUpdatedNotificationCommand: SendPasswordUpdatedCmd,
},
verifyNotification: {
sendVerifyNotificationCommand: SendVerifyCmd,
},
},
},
settings: {
role: { adminRoleName: 'admin', defaultUserRoleName: 'user' },
email: { from: 'noreply@example.com', baseUrl: 'http://localhost:3000', templates: { /* ... */ } },
otp: { assignment: 'userOtp' as const, category: 'auth-login', type: 'uuid' as const, expiresIn: '1h' },
},
}),
};
const rocketsAuth = defineRocketsAuth(rocketsAuthInput);
const rocketsAuthResources = buildRocketsAuthResources(
rocketsAuthInput.persistence,
rocketsAuthInput.invitationEntity,
);
@Module({
imports: [
EventModule.forRoot({}),
RocketsModule.forRoot({
auth: rocketsAuth,
repository: repo,
resources: [...rocketsAuthResources, /* your defineResource bundles */],
}),
],
})
export class AppModule {}defineTypeOrmRepository is the same app-local RepositoryBootstrap helper used with @bitwild/rockets (wrap TypeOrmModule.forRoot + TypeOrmRepositoryModule.forFeature). Run yarn sample-auth:dev from the monorepo root for a full working app.
AccessControlServiceInterface lives in @bitwild/rockets-auth (re-exported). Implement getUserRoles by reading userRoles off the request — RocketsJwtAuthAdapter populates that shape from the user-role join automatically.
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { AccessControlServiceInterface } from '@bitwild/rockets-auth';
@Injectable()
export class ACService implements AccessControlServiceInterface {
async getUser<T>(ctx: ExecutionContext): Promise<T> {
return ctx.switchToHttp().getRequest().user as T;
}
async getUserRoles(ctx: ExecutionContext): Promise<string[]> {
const user = await this.getUser<{ userRoles?: { role: { name: string } }[] }>(ctx);
if (!user) throw new UnauthorizedException();
return user.userRoles?.map((ur) => ur.role.name) ?? [];
}
}Pass it to accessControl.service inside defineRocketsAuth({ ... }).
Each admin operation has an abstract base class. Extend, then point the override slot at it.
import { AbstractSignupUserHandler, SignupUserCommand } from '@bitwild/rockets-auth';
@CommandHandler(SignupUserCommand)
export class SignupWithReferralHandler extends AbstractSignupUserHandler {
async execute(cmd: SignupUserCommand) {
const user = await super.execute(cmd);
await this.referralService.attach(user.id, cmd.referralCode);
return user;
}
}
defineRocketsAuth({
// ...
userCrud: {
model: UserDto,
dto: { createOne: UserCreateDto, updateOne: SampleUserUpdateDto },
handlers: { signupHandler: SignupWithReferralHandler },
},
});Available slots: signupHandler, adminList, adminRead, adminUpdate, adminDelete (all under userCrud.handlers).
When you ship your own variant, opt the built-in out via extras.disableController:
RocketsModule.forRoot({
auth: rocketsAuth,
disableController: { admin: true, invitation: true },
});Available flags: otp, signup, admin, adminRoles, invitation, invitationAcceptance, invitationRevocation, invitationReattempt, mePassword, token. (The disableController field on RocketsAuthModule.forRootAsync directly accepts the same shape; defineRocketsAuth propagates it.)
By default, RocketsModule opts in AuthServerGuard as APP_GUARD. To leave the guard wholly to the upstream @concepta/nestjs-authentication (recommended when you use this package's full stack):
defineRocketsAuth({
// ...
rocketsDefaults: { enableGlobalGuard: false },
});The upstream AuthenticationModule registers its own APP_GUARD (JwtGuard). Forward extras through extras.auth.appGuard: false if you want zero global guard.
Every factory-built controller accepts a controller.classDecorators array and a controller.routes[*].decorators map. Use them to attach throttling, ACL decorators, or rate limits.
defineRocketsAuth({
// ...
otp: {
controller: {
routes: {
issue: { decorators: [Throttle({ default: { limit: 3, ttl: 60 } })] },
verify: { decorators: [Throttle({ default: { limit: 10, ttl: 60 } })] },
},
},
},
});The same pattern applies to extras.auth.controller (for /me/password), extras.invitation.controllers.*, and extras.role.controller (admin role mgmt).
@concepta/nestjs-* motor |
Role in defineRocketsAuth |
|---|---|
user |
User CRUD, signup, admin users |
password |
Login, refresh, password change, recovery |
otp |
OTP issue / verify |
role |
Role admin CRUD |
invitation |
Invitations + acceptance |
federated |
Federated identity rows |
email / event |
Mailer hooks, domain events |
authentication |
Shared auth types/utilities |
access-control |
RBAC (re-exported from this package for convenience) |
Shared stack (path A and B): repository + CRUD + hooks still run through @bitwild/rockets-core and the same repository / resources[] options on RocketsModule.forRoot.
This package does not depend on @bitwild/rockets — your app imports both when you need built-in auth HTTP and /me.
| Symbol | Purpose |
|---|---|
defineRocketsAuth(input) |
Returns an AuthBootstrap for RocketsModule.forRoot({ auth }). Pair with buildRocketsAuthResources() on resources for auth persistence rows; RocketsAuthModule mounts via the bootstrap's adapter wiring. |
buildRocketsAuthResources(persistence, invitationEntity?) |
Converts auth persistence config into defineModuleResource rows for resources[]. |
RocketsAuthModule.forRoot(options) / forRootAsync(options) |
Direct registration. Use only when you need to mount the auth module outside the RocketsModule composition. |
RocketsJwtAuthAdapter |
The default JWT adapter validated by the chain. Picked by defineRocketsAuth unless authAdapter is overridden. |
| Field | Type | Required | Purpose |
|---|---|---|---|
persistence.module |
RepositoryModuleInterface |
yes | Same adapter instance as RocketsModule.forRoot({ repository }) — typically a defineTypeOrmRepository(...) bootstrap, not raw TypeOrmRepositoryModule alone. |
persistence.entities |
{ user, userCredentials?, userOtp?, role?, userRole?, federatedIdentity? } |
yes | Entity classes for the auth tables. Provide what you use. |
invitationEntity |
Type |
optional | Adds an invitation repository row + enables invitation routes. |
userMetadata |
RocketsUserMetadataConfig |
yes | Forwarded to /me; also used as the default userCrud.userMetadataConfig. |
userCrud |
UserCrudOptionsExtrasInterface |
yes | model, dto.createOne / updateOne, handlers, controller extras. |
roleCrud |
RoleCrudOptionsExtrasInterface |
optional | Same shape, for the role admin routes. |
authAdapter |
Type<AuthAdapterInterface> |
optional | Override the JWT adapter (e.g. inject a custom claim transformer). |
rocketsDefaults.enableGlobalGuard |
boolean |
optional | Hint to RocketsModule about the global guard default. |
| All other fields | inherited from RocketsAuthOptionsInterface |
optional | useFactory / useExisting, plus settings, authentication, user, password, otp, email, crud, role, invitation, federated, services, accessControl, disableController, ports. |
| Field | Purpose |
|---|---|
settings |
Rockets-specific settings (role names, OTP defaults, email templates). |
authentication |
Forwarded to @concepta/nestjs-authentication. Includes settings.{jwt, strategies, mfa, guards} and ports.*. Notification ports must be supplied (no silent default). |
user, password, otp, email, crud, role, federated, invitation |
Per-module config blocks, forwarded as-is to upstream modules. |
services.mailerService |
Required mailer adapter. Use a logger fallback for dev. |
services.userAccessQueryService |
Optional CanAccess for access-control queries. |
swagger |
Forwarded to SwaggerUiModule. |
| Field | Purpose |
|---|---|
accessControl |
AccessControlOptionsInterface + imports + queryServices — enables the global ACL guard wiring. |
disableController |
Drop built-in controllers (otp, signup, admin, adminRoles, invitation, invitationAcceptance, invitationRevocation, invitationReattempt, mePassword, token). |
ports |
RocketsAuthPortsConfigInterface — per-handler overrides for cross-module Command/Query plumbing. |
auth.appGuard |
Override the global APP_GUARD from AuthenticationModule. |
auth.controller / otp.controller / invitation.controllers.* / role.controller |
Per-controller decorator extras (classDecorators, routes[*].decorators). |
Every public type and CQRS class from the auth, user, otp, role, and invitation domains is re-exported under the package root:
- Auth:
SignupUserCommand,AbstractSignupUserHandler,MePasswordControllerfactory,RocketsAuthTokenController,RocketsJwtAuthAdapter. - User:
AbstractAdminUserListHandler,AbstractAdminUserReadHandler,AbstractAdminUserUpdateHandler,AbstractAdminDeleteUserHandler,RocketsAuthUserInterface,RocketsAuthUserMetadata*Interface. - Role:
RocketsAuthRoleInterface, role CRUD entities and DTOs. - OTP:
OtpModulere-export, OTP controllers and extras. - Invitation: invitation entities, DTOs, controllers, and the four factory-built controllers (
invitation,acceptance,revocation,reattempt).
Saved here so consumers don't dual-import from @concepta/nestjs-access-control:
AccessControlModule, AccessControlGuard, AccessControlFilter, AccessControlContext, AccessControlService, every @AccessControl{Create,Read,Update,Replace,Delete,Recover}* decorator, @AccessControlGrant, @AccessControlQuery, ActionEnum, PossessionEnum, AccessControlAction, CanAccess, AccessControlOptionsInterface, AccessControlContextInterface.
- OAuth providers (Apple, Google, GitHub) are deferred — upstream
@concepta/nestjs-auth-{apple,google,github,router}have not been ported to v8. The foldersrc/domains/oauth/is parked with the v7 wiring preserved as a comment andTODO(upstream:)markers.extras.auth.guardsexists for forward-compat plumbing but routes resolve only after the upstream ports ship. - Email module is on v7 (
@concepta/nestjs-email@7.0.0-alpha.10) and@concepta/nestjs-access-control@7.0.0-alpha.10— the cross-version mix is intentional while the v8 port is in flight. No code change required when those land.
Dump OpenAPI from a running auth app: yarn generate-swagger at the monorepo root (uses the rockets-auth-swagger CLI bin).
BSD-3-Clause