diff --git a/packages/adapter-chiselstrike/README.md b/packages/adapter-chiselstrike/README.md
new file mode 100644
index 0000000000..498d07a072
--- /dev/null
+++ b/packages/adapter-chiselstrike/README.md
@@ -0,0 +1,79 @@
+
+
+
+
ChiselStrike Adapter - NextAuth.js
+
+ Open Source. Full Stack. Own Your Data.
+
+
+
+
+
+
+
+
+## Overview
+
+This is the ChiselStrike Adapter for [`next-auth`](https://next-auth.js.org). This package can only be used in conjunction with the primary next-auth package. It is not a standalone package.
+
+## Getting Started
+
+Install `next-auth` and `@next-auth/chiselstrike-adapter` in your project:
+```js
+npm install next-auth @next-auth/chiselstrike-adapter
+```
+
+Configure NextAuth with the ChiselStrike adapter and a session callback to record the user ID. In
+`pages/api/auth/[...nextauth].js`:
+```js
+const adapter = ChiselStrikeAdapter(new ChiselStrikeAuthFetcher(, ))
+
+export default NextAuth({
+ adapter,
+ /// ...
+ callbacks: {
+ async session({ session, token, user }) {
+ session.userId = user.id
+ return session
+ }
+ }
+})
+
+```
+
+When accessing ChiselStrike endpoints, you can provide the user ID value in a `ChiselUID` header. This is how
+ChiselStrike knows which user is logged into the current session. For example:
+
+```js
+await fetch('//', { headers: { "ChiselUID": session.userId } })
+```
+
+## Contributing
+
+Initial setup:
+```bash
+git clone git@github.com:nextauthjs/next-auth.git
+cd next-auth
+pnpm i
+pnpm build
+cd packages/adapter-chiselstrike
+pnpm i
+```
+
+Before running a build/test cycle, please set up a ChiselStrike backend, either locally or in the cloud. If locally, please create/edit a `.env` file in the directory where `chiseld` runs and put the following line in it:
+```json
+{ "CHISELD_AUTH_SECRET" : "1234" }
+```
+If running a ChiselStrike backend in the cloud, please edit all instances of `new ChiselStrikeAuthFetcher` in the `tests/` directory to reflect your backend's URL and auth secret.
+
+Build and test:
+```bash
+cd next-auth/packages/adapter-chiselstrike/
+pnpm test -- tests/basic.test.ts
+pnpm test -- tests/custom.test.ts
+```
+(Unfortunately, the custom tests interfere with basic tests if run in parallel, so we run them separately.)
+
+## License
+
+ISC
diff --git a/packages/adapter-chiselstrike/jest.config.js b/packages/adapter-chiselstrike/jest.config.js
new file mode 100644
index 0000000000..5b8f8fcc1e
--- /dev/null
+++ b/packages/adapter-chiselstrike/jest.config.js
@@ -0,0 +1,5 @@
+/** @type { import('@jest/types').Config.InitialOptions } */
+module.exports = {
+ preset: "@next-auth/adapter-test/jest",
+ setupFilesAfterEnv: ["./tests/jest-setup.js"]
+};
diff --git a/packages/adapter-chiselstrike/logo.svg b/packages/adapter-chiselstrike/logo.svg
new file mode 100644
index 0000000000..122ad76256
--- /dev/null
+++ b/packages/adapter-chiselstrike/logo.svg
@@ -0,0 +1,9 @@
+
diff --git a/packages/adapter-chiselstrike/package.json b/packages/adapter-chiselstrike/package.json
new file mode 100644
index 0000000000..ef0f6d0d85
--- /dev/null
+++ b/packages/adapter-chiselstrike/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "@next-auth/chiselstrike-adapter",
+ "version": "0.10.0",
+ "description": "ChiselStrike adapter for next-auth.",
+ "homepage": "https://next-auth.js.org",
+ "repository": "https://github.com/nextauthjs/next-auth",
+ "bugs": {
+ "url": "https://github.com/nextauthjs/next-auth/issues"
+ },
+ "author": "Dejan Mircevski",
+ "main": "dist/index.js",
+ "license": "ISC",
+ "keywords": [
+ "next-auth",
+ "next.js",
+ "oauth",
+ "chiselstrike"
+ ],
+ "private": false,
+ "publishConfig": {
+ "access": "public"
+ },
+ "scripts": {
+ "build": "tsc",
+ "test": "jest"
+ },
+ "files": [
+ "README.md",
+ "dist"
+ ],
+ "peerDependencies": {
+ "next-auth": "^4.6.0"
+ },
+ "devDependencies": {
+ "@next-auth/adapter-test": "workspace:^0.0.0",
+ "@next-auth/tsconfig": "workspace:^0.0.0",
+ "@types/jest": "^26.0.24",
+ "jest": "^27.4.3",
+ "next-auth": "workspace:*",
+ "node-fetch": "^2.6.1"
+ }
+}
diff --git a/packages/adapter-chiselstrike/src/index.ts b/packages/adapter-chiselstrike/src/index.ts
new file mode 100644
index 0000000000..503e3f4e1f
--- /dev/null
+++ b/packages/adapter-chiselstrike/src/index.ts
@@ -0,0 +1,193 @@
+import type { Account } from "next-auth";
+import type { Adapter, AdapterSession, AdapterUser, VerificationToken } from "next-auth/adapters"
+
+export class ChiselStrikeAuthFetcher {
+ url: string; /// ChiselStrike backend.
+ secret: string;
+
+ /// Makes a fetcher pointing to the ChiselStrike backend at this URL. Use the secret provided by the ChiselStrike platform.
+ constructor(url: string, secret: string) {
+ this.url = url
+ this.secret = secret
+ }
+
+ /// Returns a ChiselStrike server auth path with this suffix.
+ private auth(suffix: string): string {
+ return `${this.url}/__chiselstrike/auth/${suffix}`
+ }
+
+ public sessions(suffix?: string): string {
+ return this.auth('sessions') + (suffix ?? '');
+ }
+
+ public accounts(suffix?: string): string {
+ return this.auth('accounts') + (suffix ?? '');
+ }
+
+ public users(suffix?: string): string {
+ return this.auth('users') + (suffix ?? '');
+ }
+
+ public tokens(suffix?: string): string {
+ return this.auth('tokens') + (suffix ?? '');
+ }
+
+ /// Like regular fetch(), but adds a header with this.secret.
+ public async fetch(input: RequestInfo, init?: RequestInit | undefined): Promise {
+ init ??= {}
+ init.headers = new Headers(init.headers)
+ init.headers.set('ChiselAuth', this.secret)
+ return fetch(input, init)
+ }
+
+ /// Fetches url and returns the first element of the resulting array (or null if anything goes wrong).
+ public async filter(url: string): Promise {
+ const res = await this.fetch(url)
+ if (!res.ok) { return null }
+ const jres = await res.json()
+ if (!Array.isArray(jres.results) || jres.results.length < 1) { return null }
+ return jres.results[0]
+ }
+
+ public async deleteEverything() {
+ await Promise.all([
+ this.fetch(this.sessions('?all=true'), { method: 'DELETE' }),
+ this.fetch(this.accounts('?all=true'), { method: 'DELETE' }),
+ this.fetch(this.users('?all=true'), { method: 'DELETE' }),
+ this.fetch(this.tokens('?all=true'), { method: 'DELETE' })
+ ])
+ }
+}
+
+export function ChiselStrikeAdapter(fetcher: ChiselStrikeAuthFetcher): Adapter {
+ return {
+ createUser: async (user: Omit): Promise => {
+ const resp = await fetcher.fetch(fetcher.users(), { method: 'POST', body: JSON.stringify(user) })
+ await ensureOK(resp, `posting user ${JSON.stringify(user)}`);
+ return userFromJson(await resp.json())
+ },
+ getUser: async (id: string): Promise => {
+ const resp = await fetcher.fetch(fetcher.users(`/${id}`))
+ if (!resp.ok) { return null }
+ return userFromJson(await resp.json())
+ },
+ getUserByEmail: async (email: string): Promise => {
+ return userFromJson(await fetcher.filter(fetcher.users(`?${makeFilter({ email })}`)))
+ },
+ updateUser: async (user: Partial): Promise => {
+ // TODO: use PATCH
+ const get = await fetcher.fetch(fetcher.users(`/${user.id}`))
+ await ensureOK(get, `getting user ${user.id}`)
+ let u = await get.json()
+ Object.keys(user).forEach(key => u[key] = user[key])
+ const body = JSON.stringify(u)
+ const put = await fetcher.fetch(fetcher.users(`/${user.id}`), { method: 'PUT', body })
+ await ensureOK(put, `writing user ${body}`)
+ return userFromJson(u)
+ },
+ getUserByAccount: async (providerAccountId: Pick): Promise => {
+ const acct = await fetcher.filter(fetcher.accounts(`?${makeFilter(providerAccountId)}`))
+ if (!acct) { return null }
+ const uresp = await fetcher.fetch(fetcher.users(`/${acct.userId}`))
+ await ensureOK(uresp, `getting user ${acct.userId}`)
+ return userFromJson(await uresp.json())
+ },
+ // deleteUser?: ((userId: string) => Promise | Awaitable) | undefined;
+ deleteUser: async (userId: string): Promise => {
+ let resp = await fetcher.fetch(fetcher.users(`/${userId}`), { method: 'DELETE' })
+ await ensureOK(resp, `deleting user ${userId}`)
+ resp = await fetcher.fetch(fetcher.sessions(`?.userId=${userId}`), { method: 'DELETE' })
+ await ensureOK(resp, `deleting sessions for ${userId}`)
+ resp = await fetcher.fetch(fetcher.accounts(`?.userId=${userId}`), { method: 'DELETE' })
+ await ensureOK(resp, `deleting accounts for ${userId}`)
+ return null
+ },
+ // linkAccount: (account: Account) => Promise | Awaitable;
+ linkAccount: async (account: Account): Promise => {
+ const body = JSON.stringify(account);
+ const resp = await fetcher.fetch(fetcher.accounts(), { method: 'POST', body })
+ await ensureOK(resp, `posting account ${body}`);
+ return (await resp.json()).results
+ },
+ // unlinkAccount?: ((providerAccountId: Pick) => Promise | Awaitable) | undefined;
+ unlinkAccount: async (providerAccountId: Pick): Promise => {
+ const resp = await fetcher.fetch(fetcher.accounts(`?${makeFilter(providerAccountId)}`), { method: 'DELETE' })
+ await ensureOK(resp, `deleting account ${JSON.stringify(providerAccountId)}`)
+ },
+ // createSession: (session: { sessionToken: string; userId: string; expires: Date; }) => Awaitable;
+ createSession: async (session: { sessionToken: string; userId: string; expires: Date; }): Promise => {
+ const str = JSON.stringify(session);
+ const resp = await fetcher.fetch(fetcher.sessions(), { method: 'POST', body: str })
+ await ensureOK(resp, `posting session ${JSON.stringify(session)}`);
+ return sessionFromJson(await resp.json())
+ },
+ // getSessionAndUser: (sessionToken: string) => Awaitable<{ session: AdapterSession; user: AdapterUser; } | null>;
+ getSessionAndUser: async (sessionToken: string): Promise<{ session: AdapterSession; user: AdapterUser; } | null> => {
+ const session = await fetcher.filter(fetcher.sessions(`?.sessionToken=${sessionToken}`))
+ if (!session) { return null }
+ const rUser = await fetcher.fetch(fetcher.users(`/${session.userId}`))
+ await ensureOK(rUser, `fetching user ${session.userId}`)
+ return { session: sessionFromJson(session), user: userFromJson(await rUser.json()) }
+ },
+ // updateSession: (session: Partial & Pick) => Awaitable;
+ updateSession: async (session: Partial & Pick): Promise => {
+ // TODO: use PATCH
+ const dbSession = await fetcher.filter(fetcher.sessions(`?.sessionToken=${session.sessionToken}`))
+ if (!dbSession) { return null }
+ Object.entries(session).forEach(([key, entry]) => {
+ if (entry instanceof Date) {
+ dbSession[key] = entry.toISOString();
+ } else {
+ dbSession[key] = entry;
+ }
+ });
+ const body = JSON.stringify(dbSession)
+ const put = await fetcher.fetch(fetcher.sessions(`/${dbSession.id}`), { method: 'PUT', body })
+ await ensureOK(put, `writing user ${body}`)
+ return dbSession as unknown as AdapterSession
+ },
+ // deleteSession: (sessionToken: string) => Promise | Awaitable;
+ deleteSession: async (sessionToken: string): Promise => {
+ const get = await fetcher.fetch(fetcher.sessions(`?${makeFilter({ sessionToken })}`), { method: 'DELETE' })
+ await ensureOK(get, `getting session with token ${sessionToken}`)
+ },
+ // createVerificationToken?: ((verificationToken: VerificationToken) => Awaitable) | undefined;
+ createVerificationToken: async (verificationToken: VerificationToken): Promise => {
+ const str = JSON.stringify(verificationToken);
+ const resp = await fetcher.fetch(fetcher.tokens(), { method: 'POST', body: str });
+ await ensureOK(resp, `posting token ${str}`)
+ const token = await resp.json()
+ return { identifier: token.identifier, token: token.token, expires: new Date(token.expires) }
+ },
+ // useVerificationToken?: ((params: { identifier: string; token: string; }) => Awaitable) | undefined;
+ useVerificationToken: async (params: { identifier: string; token: string; }): Promise => {
+ const token = await fetcher.filter(fetcher.tokens(`?${makeFilter(params)}`))
+ if (!token) { return null }
+ await fetcher.fetch(fetcher.tokens(`/${token.id}`), { method: 'DELETE' })
+ return { identifier: token.identifier, token: token.token, expires: new Date(token.expires) }
+ }
+ }
+}
+
+function userFromJson(json: any): AdapterUser {
+ const user = json?.results ?? json
+ return user ? { ...user, emailVerified: new Date(user.emailVerified) } : null
+}
+
+async function ensureOK(resp: Response, during: string) {
+ if (!resp.ok) {
+ const msg = `Error ${resp.status} during ${during}: ${await resp.text()}`;
+ console.error(msg);
+ throw msg;
+ }
+}
+
+function sessionFromJson(json: any): AdapterSession {
+ const session = json?.results ?? json
+ return { ...session, expires: new Date(session.expires), id: session.id ?? 'impossible: fetched CSession has null ID' }
+}
+
+function makeFilter(propertyValues: Record) {
+ let filters = Object.entries(propertyValues).map(e => `.${e[0]}=${e[1]}`)
+ return filters.join('&')
+}
\ No newline at end of file
diff --git a/packages/adapter-chiselstrike/tests/basic.test.ts b/packages/adapter-chiselstrike/tests/basic.test.ts
new file mode 100644
index 0000000000..df21cf0f08
--- /dev/null
+++ b/packages/adapter-chiselstrike/tests/basic.test.ts
@@ -0,0 +1,40 @@
+import { runBasicTests } from "@next-auth/adapter-test"
+import { ChiselStrikeAdapter, ChiselStrikeAuthFetcher } from "../src"
+
+const fetcher = new ChiselStrikeAuthFetcher('http://localhost:8080', '1234')
+const adapter = ChiselStrikeAdapter(fetcher)
+
+runBasicTests(
+ {
+ adapter,
+ db: {
+ connect: async () => {
+ await fetcher.deleteEverything()
+ },
+ session: async (sessionToken: string) => {
+ const s = await fetcher.filter(fetcher.sessions(`?.sessionToken=${sessionToken}`))
+ return s ? { ...s, expires: new Date(s.expires) } : null
+ },
+ user: async (id: string) => {
+ const resp = await fetcher.fetch(fetcher.users(`/${id}`))
+ if (!resp.ok) { return null }
+ const json = await resp.json()
+ return { ...json, emailVerified: new Date(json.emailVerified) }
+ },
+ account: async (providerAccountId: { provider: string; providerAccountId: string }) => {
+ const providerFilter = providerAccountId.provider ? `.provider=${providerAccountId.provider}` : ''
+ const providerAccountIdFilter =
+ providerAccountId.providerAccountId ? `.providerAccountId=${providerAccountId.providerAccountId}` : ''
+ return await fetcher.filter(
+ fetcher.accounts(`?${providerFilter}&${providerAccountIdFilter}`))
+ },
+ verificationToken: async (params: { identifier: string; token: string }) => {
+ const idFilter = `.identifier=${params.identifier}`
+ const tokenFilter = `.token=${params.token}`
+ let token = await fetcher.filter(
+ fetcher.tokens(`?${idFilter}&${tokenFilter}`))
+ return token ? { ...token, expires: new Date(token.expires), id: undefined } : null
+ }
+ }
+ }
+)
\ No newline at end of file
diff --git a/packages/adapter-chiselstrike/tests/custom.test.ts b/packages/adapter-chiselstrike/tests/custom.test.ts
new file mode 100644
index 0000000000..4bc78d5ca5
--- /dev/null
+++ b/packages/adapter-chiselstrike/tests/custom.test.ts
@@ -0,0 +1,134 @@
+/** @file
+ * Unit tests for the ChiselStrike adapter for NextAuth.js. To run, first create a .env file
+ * with the following contents:
+ *
+ * { "CHISELD_AUTH_SECRET" : "1234" }
+ *
+ * Start `chiseld` in the directory of that .env file, then run `jest` here in this directory.
+ */
+
+import { Account } from "next-auth"
+import { ChiselStrikeAdapter, ChiselStrikeAuthFetcher } from "../src"
+export { }
+
+const fetcher = new ChiselStrikeAuthFetcher('http://localhost:8080', '1234')
+const a = ChiselStrikeAdapter(fetcher)
+
+beforeEach(async () => {
+ await fetcher.deleteEverything()
+})
+
+test('createUser returns payload', async () => {
+ const uMinimal = await a.createUser({ emailVerified: '2022-03-01' })
+ expect(uMinimal).not.toBeNull()
+ expect(uMinimal?.emailVerified).toStrictEqual(new Date('2022-03-01'))
+
+ const u2 = await a.createUser({ emailVerified: '2022-03-02', name: 'Bono', email: 'a@b.co', image: '' })
+ expect(u2).not.toBeNull()
+ expect(u2?.emailVerified).toStrictEqual(new Date('2022-03-02'))
+ expect(u2?.name).toBe('Bono')
+ expect(u2?.email).toBe('a@b.co')
+ expect(u2?.image).toBe('')
+})
+
+test('getUser gets the right user', async () => {
+ const created = await a.createUser({ emailVerified: '2022-03-01', name: 'Johnny' })
+ expect(created).not.toBeNull()
+ if (!created) return
+ const obtained = await a.getUser(created.id)
+ expect(obtained).toStrictEqual(created)
+})
+
+test('getUser returns null for non-existent ID', async () => {
+ expect(await a.getUser('never-going-to-exist-because-this-is-just-a-test')).toBeNull()
+})
+
+test('getUserByEmail gets the right user', async () => {
+ const email = 'dejan@chiselstrike.com'
+ const u1 = await a.createUser({ emailVerified: '2022-03-03', email, name: 'Heimdall' })
+ expect(u1).not.toBeNull()
+ const u2 = await a.createUser({ emailVerified: '2022-03-03', email: '', name: 'Heimdall' });
+ expect(u2).not.toBeNull()
+ expect(await a.getUserByEmail(email)).toStrictEqual(u1)
+ expect(await a.getUserByEmail('')).toStrictEqual(u2)
+})
+
+test('getUserByEmail returns null for non-existing email', async () => {
+ expect(await a.getUserByEmail('some absolutely non-existing email')).toBeNull()
+})
+
+test('updateUser updates correctly', async () => {
+ const id = (await a.createUser({ emailVerified: '2022-03-03', name: 'Popeye' }))?.id
+ const res = await a.updateUser({ id, name: 'Bluto', image: 'jpg' })
+ expect(res).toStrictEqual({ name: 'Bluto', image: 'jpg', emailVerified: new Date('2022-03-03'), id })
+ if (!id) { return }
+ const get = await a.getUser(id)
+ expect(get).toStrictEqual(res)
+})
+
+test('updateUser rejects non-existing id', async () => {
+ await expect(a.updateUser({ id: 'something completely unexpected and unique', email: 'rhododendron' })).rejects.toMatch('completely unexpected')
+})
+
+test('linkAccount results in getUserByAccount finding the right account', async () => {
+ const ghUser = await a.createUser({ emailVerified: '2022-03-03', name: 'GHUser' })
+ await a.linkAccount({ provider: 'github', providerAccountId: '1234', userId: ghUser.id, type: 'oauth' })
+ const twUser = await a.createUser({ emailVerified: '2022-03-03', name: 'TwUser' })
+ await a.linkAccount({ provider: 'twitter', providerAccountId: '4321', userId: twUser.id, type: 'oauth' })
+ expect(await a.getUserByAccount({ provider: 'github', providerAccountId: '1234' })).toStrictEqual(ghUser)
+ expect(await a.getUserByAccount({ provider: 'twitter', providerAccountId: '4321' })).toStrictEqual(twUser)
+})
+
+test('getUserByAccount returns null on mismatch', async () => {
+ expect(await a.getUserByAccount({ providerAccountId: 'impossible to exist', provider: 'fffff' })).toBeNull()
+})
+
+test('user deletion works', async () => {
+ const u = await a.createUser({ emailVerified: '2022-03-04', name: 'Tinker Bell' })
+ expect(await a.getUser(u.id)).toStrictEqual(u)
+ expect(await a.deleteUser(u.id)).toBeNull()
+ expect(await a.getUser(u.id)).toBeNull()
+})
+
+test('unlinkAccount makes getUserByAccount not find the account (but getUser still finds the user)', async () => {
+ const u = await a.createUser({ emailVerified: '2022-03-03', name: 'Popeye' })
+ const acct = { provider: 'github', providerAccountId: '1234', userId: u.id, type: 'oauth' } as Account
+ await a.linkAccount(acct)
+ expect(await a.getUserByAccount({ provider: 'github', providerAccountId: '1234' })).toStrictEqual(u)
+ await a.unlinkAccount(acct)
+ expect(await a.getUserByAccount({ provider: 'github', providerAccountId: '1234' })).toBeNull()
+ expect(await a.getUser(u.id)).toStrictEqual(u)
+})
+
+test('createSession makes getSessionAndUser find the created session', async () => {
+ const user = await a.createUser({ emailVerified: '2022-03-07', name: 'Oscar' })
+ await a.createSession({ sessionToken: 'dummy', expires: new Date, userId: user.id })
+ const session = await a.createSession({ sessionToken: 'token1', expires: new Date, userId: user.id })
+ expect(await a.getSessionAndUser('token1')).toStrictEqual({ user, session })
+})
+
+test('updateSession updates correctly', async () => {
+ const user = await a.createUser({ emailVerified: '2022-03-07', name: 'Oscar' })
+ const s1 = await a.createSession({ sessionToken: 's1', expires: new Date, userId: user.id })
+ const s2 = await a.createSession({ sessionToken: 's2', expires: new Date(10000), userId: user.id })
+ await a.updateSession({ sessionToken: 's2', expires: new Date(50000) })
+ expect(await a.getSessionAndUser('s2')).toStrictEqual({ user, session: { ...s2, expires: new Date(50000) } })
+ expect(await a.getSessionAndUser('s1')).toStrictEqual({ user, session: s1 })
+})
+
+test('deleteSession deletes the right session', async () => {
+ const user = await a.createUser({ emailVerified: '2022-03-07', name: 'Oscar' })
+ const s1 = await a.createSession({ sessionToken: 's1', expires: new Date, userId: user.id })
+ const s2 = await a.createSession({ sessionToken: 's2', expires: new Date, userId: user.id })
+ await a.deleteSession('s1')
+ expect(await a.getSessionAndUser('s1')).toBeNull()
+ expect(await a.getSessionAndUser('s2')).toStrictEqual({ user, session: s2 })
+})
+
+test('useVerificationToken finds token, deletes it', async () => {
+ const user = await a.createUser({ emailVerified: '2022-03-07', name: 'Oscar' })
+ await a.createVerificationToken({ expires: new Date('2022-03-10'), identifier: '321', token: 'dummy' })
+ const token = await a.createVerificationToken({ expires: new Date('2022-03-10'), identifier: '123', token: 'abc' })
+ expect(await a.useVerificationToken({ identifier: '123', token: 'abc' })).toStrictEqual(token)
+ expect(await a.useVerificationToken({ identifier: '123', token: 'abc' })).toBeNull()
+})
diff --git a/packages/adapter-chiselstrike/tests/jest-setup.js b/packages/adapter-chiselstrike/tests/jest-setup.js
new file mode 100644
index 0000000000..96c9aa1a31
--- /dev/null
+++ b/packages/adapter-chiselstrike/tests/jest-setup.js
@@ -0,0 +1,3 @@
+import fetch from "node-fetch"
+globalThis.fetch = fetch;
+globalThis.Headers = fetch.Headers;
diff --git a/packages/adapter-chiselstrike/tsconfig.json b/packages/adapter-chiselstrike/tsconfig.json
new file mode 100644
index 0000000000..bf0a7cabce
--- /dev/null
+++ b/packages/adapter-chiselstrike/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@next-auth/tsconfig/adapters.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist"
+ },
+ "exclude": ["tests", "dist", "jest.config.js"]
+}