npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nathapp/nestjs-oauth-prisma

v1.1.1

Published

Prisma adapter for @nathapp/nestjs-oauth

Readme

@nathapp/nestjs-oauth-prisma

Prisma adapter for @nathapp/nestjs-oauth. Binds the core package's repository tokens to AbstractPrismaRepository-based implementations, plus the default SIGNING_KEY_PROVIDER.

Install

npm i @nathapp/nestjs-oauth-prisma
npm i -D prisma && npx prisma init

Schema

Append the 13 models from the packaged fragment node_modules/@nathapp/nestjs-oauth-prisma/prisma/oauth.prisma to your app's schema.prisma. The fragment carries no datasource or generator block — it is models only. Every DateTime in it uses @db.Timestamptz(6) (PostgreSQL timestamptz); bare DateTime is not supported — token expiry comparisons misfire when the database server timezone is not UTC.

PostgreSQL and MySQL. The fragment is authored for PostgreSQL 16 and also supports MySQL 8.0. For MySQL, assemble it with @db.Timestamptz(6) rewritten to @db.DateTime(6) (the only change; see the @nathapp/nestjs-iam-prisma README, "Schema assembly and database support") and keep the MySQL server and sessions on UTC (time_zone = '+00:00'), because DATETIME(6) stores no timezone. Upgrading a MySQL database built from an earlier fragment: apply prisma/sql/long-text-columns.mysql.sql first (PostgreSQL needs nothing); on MySQL the signing-key PEM columns did not fit before it, so no key could be created. Each packaged migration has a migration.mysql.sql twin next to its migration.sql; on MySQL the one-active-signing-key guard is a functional unique index. Signing keys are per tenant (tenant_id NULL is the global key), so the guard is one active key per tenant: apply 20261004_oauth_signing_keys_one_active_per_tenant, which replaces the global index from 20260608_oauth_signing_keys_one_active. On a multi-tenant database with the old global index only one tenant could ever hold an active key. oauth_scopes.code compares case-insensitively under MySQL's default collation, so Read and read collide there; keep scope codes lower-case.

prisma/migrations/ in this package holds six incremental patches (oauth_signing_keys_one_active, oauth_client_advanced_auth_fields, oauth_resource_indicator, oauth_token_exchange_actor, oauth_access_token_mtls_binding, oauth_signing_keys_one_active_per_tenant). They are deltas against a database already built from the fragment, not a complete initial migration set, and Prisma will not apply them for you — the consumer's migration history lives in the consumer's own project. Apply them by hand when upgrading an existing database, after prisma migrate dev has produced your own migration from the fragment.

Register

import { Module } from '@nestjs/common';
import { PrismaPg } from '@prisma/adapter-pg'; // MySQL: PrismaMariaDb from '@prisma/adapter-mariadb' (host/port/user/password/database, no connectionString)
import { PrismaClient } from './generated/prisma/client'; // your generator `output`
import { ClockModule, IdGeneratorModule } from '@nathapp/nestjs-common';
import { PrismaModule } from '@nathapp/nestjs-prisma';
import { OAuthPrismaModule } from '@nathapp/nestjs-oauth-prisma';

@Module({
  imports: [
    PrismaModule.forRoot({
      client: PrismaClient,
      clientOptions: { adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL! }) },
      transaction: true,
    }),
    ClockModule.register(),        // provides CLOCK, required by PrismaSigningKeyProvider
    IdGeneratorModule.register(),  // provides ID_GENERATOR, same requirement
    OAuthPrismaModule.register(),
  ],
})
export class AppModule {}

ClockModule.register() and IdGeneratorModule.register() are not optional for this adapter. PrismaSigningKeyProvider injects CLOCK and ID_GENERATOR non-optionally, and the { provide: SIGNING_KEY_PROVIDER, useExisting: PrismaSigningKeyProvider } binding that pulls it in is unconditional — not behind any option flag. Omit either module and Nest reports Nest can't resolve dependencies of the PrismaSigningKeyProvider (Symbol(OAUTH_SIGNING_KEY_REPOSITORY), Symbol(TRANSACTION_MANAGER), ?, Symbol(CLOCK)). Please make sure that the argument Symbol(ID_GENERATOR) at index [2] is available in the OAuthPrismaModule module. This applies to the bare-class import form too: imports: [OAuthPrismaModule] constructs the same provider and needs both modules.

register() takes an optional options object:

| Option | Type | Default | Meaning | |---|---|---|---| | global | boolean | true | Register as a global module so the repository tokens resolve anywhere. |

register() was added alongside the bare-class form, not in place of it. There is no registerAsync.

Both import forms

This adapter is unusual: unlike the other four Prisma adapters, it supports two import forms.

imports: [OAuthPrismaModule]              // still supported, non-global
imports: [OAuthPrismaModule.register()]   // global by default

OAuthPrismaModule carries a @Module({ providers, exports }) decorator as well as the register() factory, so the bare class is a valid import. It was historically the only form, and existing consumers and tests use it that way. The bare class is not global: modules that inject these tokens must import it themselves.

Prefer OAuthPrismaModule.register(). It is global by default, so the OAuth tokens resolve anywhere without a per-module import, and it leaves room for the global option. Migrating from the bare class to register() is a one-line change per module and does not require touching token wiring.

Do not use both forms in one imports array.

imports: [OAuthPrismaModule, OAuthPrismaModule.register()]  // two instances

The dynamic-module token hash differs from the static class token, so Nest treats them as two separate modules and every repository is instantiated twice. Nothing warns about it. Pick one form per application.

If you already wrap this adapter in @Global()

Pass global: false. Otherwise the adapter's own global exports are injected in addition to your override, and last-registration-wins silently decides which implementation is used:

import { Global, Module } from '@nestjs/common';
import { ClockModule, IdGeneratorModule } from '@nathapp/nestjs-common';
import { OAUTH_CLIENT_REPOSITORY } from '@nathapp/nestjs-oauth';
import { OAuthPrismaModule } from '@nathapp/nestjs-oauth-prisma';
import { AuditedOAuthClientRepository } from './audited-oauth-client.repository';

@Global()
@Module({
  imports: [
    ClockModule.register(),        // required by PrismaSigningKeyProvider
    IdGeneratorModule.register(),  // required by PrismaSigningKeyProvider
    OAuthPrismaModule.register({ global: false }),
  ],
  providers: [{ provide: OAUTH_CLIENT_REPOSITORY, useClass: AuditedOAuthClientRepository }],
  exports: [OAUTH_CLIENT_REPOSITORY],
})
export class PrismaOverridesModule {}

global: false only helps if the adapter is registered once. A second, global register() elsewhere in the app keeps its global exports, and global: false here does not remove them — so the collision this section exists to prevent is still there. Register the adapter in exactly one place.

The global option carries no documentation in the published type definitions (removeComments: true strips the JSDoc from the emitted .d.ts), so this README is where it is described. The options object is typed as PrismaAdapterModuleOptions, which is exported from this package if you need to name the type.

Transactions

Multi-write flows need PrismaModule.forRoot({ client: PrismaClient, transaction: true }) with PrismaClient imported from your generated client (Prisma 7 prisma-client generator). Every repository resolves its client per operation from the TRANSACTION_MANAGER token, and the two PrismaModule registration paths fail differently when transaction is omitted:

  • PrismaModule.forRoot() binds TRANSACTION_MANAGER only when transaction: true is set. Without it nothing is bound, the repositories cannot resolve their dependency, and Nest fails at boot.
  • PrismaModule.forRootAsync() always binds the token, but with a manager whose run() and getClient() throw and whose isInTransaction() returns false, so the app boots and fails on first use instead.

Neither path is a silent passthrough: a real transaction manager either joins the transaction already in scope or opens a Prisma interactive transaction.

Both of those are PrismaModule from @nathapp/nestjs-prisma. These adapters expose only the synchronous register(); they have no registerAsync.

The refresh grant is the case that matters most: it revokes the old refresh token, issues a replacement, and issues an access token inside a single run(). If that is not one real transaction, a failure midway leaves the client with no usable token and a revoked grant.

Tokens bound

This package does not re-export @nathapp/nestjs-oauth. Import the tokens themselves from @nathapp/nestjs-oauth, and only the implementations and the module from this package.

| Token | Implementation | |---|---| | OAUTH_CLIENT_REPOSITORY | PrismaOAuthClientRepository | | OAUTH_SCOPE_REPOSITORY | PrismaOAuthScopeRepository | | OAUTH_AUTH_CODE_REPOSITORY | PrismaOAuthAuthorizationCodeRepository | | OAUTH_ACCESS_TOKEN_REPOSITORY | PrismaOAuthAccessTokenRepository | | OAUTH_REFRESH_TOKEN_REPOSITORY | PrismaOAuthRefreshTokenRepository | | OAUTH_CONSENT_REPOSITORY | PrismaOAuthConsentRepository | | OAUTH_SIGNING_KEY_REPOSITORY | PrismaOAuthSigningKeyRepository | | OAUTH_DEVICE_CODE_REPOSITORY | PrismaOAuthDeviceCodeRepository | | OAUTH_PUSHED_REQUEST_REPOSITORY | PrismaOAuthPushedRequestRepository | | SIGNING_KEY_PROVIDER | PrismaSigningKeyProvider |

PrismaSigningKeyProvider reads the active OAuthSigningKey row on every call — it holds no cache. rotate() marks the current key retiring with a 7-day grace window and mints a new RSA/RS256 keypair in one run(); getVerificationKeys() publishes the active and retiring keys in the JWKS. An initial key is minted lazily on first use, so pre-seed a key or call rotate() at deploy to avoid a race between concurrent first calls. Private keys are stored as PEM: override encryptPrivateKey() / decryptPrivateKey() for KMS or Vault. The defaults refuse plaintext storage in production unless OAUTH_ALLOW_PLAINTEXT_SIGNING_KEYS=true is set.

The module also provides and exports the class PrismaConsentLogoutTargetResolver, without binding it to anything. To use it as your LOGOUT_TARGET_RESOLVER, bind the token yourself:

import { Module } from '@nestjs/common';
import { ClockModule, IdGeneratorModule } from '@nathapp/nestjs-common';
import { LOGOUT_TARGET_RESOLVER } from '@nathapp/nestjs-oauth';
import { OAuthPrismaModule, PrismaConsentLogoutTargetResolver } from '@nathapp/nestjs-oauth-prisma';

@Module({
  imports: [
    ClockModule.register(),        // required by PrismaSigningKeyProvider
    IdGeneratorModule.register(),  // required by PrismaSigningKeyProvider
    OAuthPrismaModule.register(),
  ],
  providers: [{ provide: LOGOUT_TARGET_RESOLVER, useExisting: PrismaConsentLogoutTargetResolver }],
})
export class LogoutTargetModule {}

Upgrade: mTLS access token bindings

Add OAuthAccessToken.cnfX5tS256 from the schema fragment to your app's schema. The packaged SQL patch prisma/migrations/20261003_oauth_access_token_mtls_binding/migration.sql adds the nullable oauth_access_tokens.cnf_x5t_s256 column. Incorporate this change into your own migration history, apply it before deploying the updated adapter, and regenerate your Prisma client.

Earlier adapters discarded the certificate thumbprint when writing access tokens, so existing rows cannot be backfilled reliably. Revoke existing access tokens belonging to mTLS clients and require fresh issuance at cutover. A null column remains valid for clients that do not use certificate binding.

Consent, device-code and pushed-request domain models continue to expose the public OAuth clientId; the adapter resolves that identifier through the client relation when writing the internal database foreign key. Access/refresh tokens and authorization codes continue to use the internal client row id.