@haatlabs-core/nest-auth
v0.10.0
Published
NestJS sign-in by emailed code, sessions, API keys, roles and the guard every route goes through.
Readme
@haatlabs-core/nest-auth
Signing in to an app's API: emailed codes, sessions, API keys, and the guard every route goes through. Configured by the app; every name comes from the app's name.
AuthModule.forRoot({
appName: 'shop', // cookies shop_session / shop_csrf, keys shop_…, Redis shop:…
displayName: 'Shop', // email copy
wholeOrganizationMessage: 'Lists are in the Shop dashboard: sign in there to use them',
stores: {
identity: { useFactory: (db) => new IdentityRepository(db.db, tables), inject: [SYSTEM_DB] },
apiKeys: { useFactory: (sys, app) => new ApiKeyRepository(sys.db, app.db, tables), inject: [SYSTEM_DB, APP_DB] },
},
apiKeyAdmission: { useFactory: (features) => apiFeatureGate(features), inject: [FeatureRepository] }, // or 'allow'
bearer: { useFactory: (embedded) => embedded, inject: [EmbeddedAuthService] }, // Shopify session tokens
meExtras: { useFactory: (features) => meFeatures(features), inject: [FeatureRepository] },
});- The guard denies by default: every route needs a principal unless
@Public(), every write needs MEMBER unless@MinRole(...)says otherwise, cookie writes need the CSRF token, the role is read again on every request, and a refused bearer token never falls back to the cookie.@StandaloneOnly()refuses the embedded app;@WholeOrganization()refuses a store-only principal (Shopify staff of a store in an organization made on the dashboard). - API keys are checked against the app's pattern (built from its escaped prefix) and,
unless
'allow', againstapiKeyAdmissionat creation and on every use. - Sign-in codes work once, for ten minutes, for the email and purpose they were sent
for, and are gone after five wrong guesses. With a fixed code (
SIGN_IN_FIXED_CODE), a mail failure still signs in. EmailSignInController(only withbearer): a store installed from Shopify gives its owner an email to sign in with, once.@haatlabs-core/nest-auth/password: argon2id for administrators' passwords (argon2is an optional peer).
Needs the app's global providers for nest-common's CONFIG, LOGGER, REDIS,
RATE_LIMITER (built with keyPrefix: '<appName>:') and MAILER.
Install
pnpm add @haatlabs-core/nest-authIt leaves these packages to your app, so the app holds one copy: @fastify/cookie, @nestjs/common, @nestjs/core, @nestjs/platform-fastify, drizzle-orm, fastify, ioredis, reflect-metadata, rxjs, zod, argon2.
@haatlabs-core/nest-auth/admin
Platform administrators' sign-in, apart from merchants': its own cookie (<appName>_admin,
SameSite=Strict, sent to /api/v1/admin only, no lifetime of its own), sessions and
guard. AdminAuthModule.forRoot({ appName, store }) gives POST /api/v1/admin/login,
POST /api/v1/admin/logout and GET /api/v1/admin/me; the app's own administrators'
routes use @Public() @UseGuards(AdminGuard) and @CurrentAdmin(). A session ends when
the account's password or access changes (platform_admins.updated_at), after two idle
hours, or twelve in all; signing in again elsewhere leaves it alone.
runAdminUserCommand(store, argv, { hashPassword }) is the operator's admin-user command.
It needs the optional peer argon2: loading /admin hashes a dummy password, so that an
unknown address takes as long to refuse as a wrong password. A cookiePath of its own must
cover /api/v1/admin, or forRoot refuses it.
@haatlabs-core/nest-auth/shopify
Connecting stores by Shopify's OAuth, and the embedded app's sign-in.
ShopifyAuthModule.forRoot({
appName: 'shop',
stores: { useFactory: (repo) => repo, inject: [SystemStoreRepository] }, // ShopifyStores
canConnect: { useFactory: (limits) => limits, inject: [StoreLimits] }, // or 'allow'
afterInstall: { useFactory: (hooks) => (shop, token) => hooks.subscribe(shop, token), inject: [WebhookSubscriptions] },
});
AuthModule.forRoot({ ..., bearer: { useFactory: (e) => e, inject: [EmbeddedAuthService] } });GET /api/v1/auth/shopify/installand/callback: the state is tied to the browser by the<appName>_oauthcookie, the callback's signature is checked, and a dashboard connection is completed by the admin who started it, with room for the store (canConnect) at both ends. A refusal a person sees is a page (CONNECT_PROBLEM_PAGE).EmbeddedAuthServiceverifies a session token, installs a shop opening the app for the first time, replaces a token that needs it, and gives staff of a store in a dashboard organization a store-only principal.- Without Shopify config, or without
TOKEN_CIPHER, the routes answer 404 and session tokens 401.
