From f9c6eb7f32a8ba18c1896e7f7b71102dd6289665 Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Mon, 16 May 2022 21:40:55 -0400
Subject: [PATCH 01/10] Initial commit
Signed-off-by: Dejan Mircevski
---
packages/adapter-chiselstrike/README.md | 79 ++++++++
packages/adapter-chiselstrike/logo.svg | 9 +
packages/adapter-chiselstrike/package.json | 47 +++++
packages/adapter-chiselstrike/src/index.ts | 191 ++++++++++++++++++
.../adapter-chiselstrike/tests/basic.test.ts | 48 +++++
.../adapter-chiselstrike/tests/custom.test.ts | 134 ++++++++++++
6 files changed, 508 insertions(+)
create mode 100644 packages/adapter-chiselstrike/README.md
create mode 100644 packages/adapter-chiselstrike/logo.svg
create mode 100644 packages/adapter-chiselstrike/package.json
create mode 100644 packages/adapter-chiselstrike/src/index.ts
create mode 100644 packages/adapter-chiselstrike/tests/basic.test.ts
create mode 100644 packages/adapter-chiselstrike/tests/custom.test.ts
diff --git a/packages/adapter-chiselstrike/README.md b/packages/adapter-chiselstrike/README.md
new file mode 100644
index 0000000000..13ad861d38
--- /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 = new CSAdapter(, )
+
+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 ChiselStrikeAdapter` 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/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..5ed3f96720
--- /dev/null
+++ b/packages/adapter-chiselstrike/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "@next-auth/chiselstrike-adapter",
+ "version": "1.0.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": {
+ "test": "jest"
+ },
+ "files": [
+ "README.md",
+ "dist"
+ ],
+ "dependencies": {
+ "cross-fetch": "^3.1.5",
+ "next-auth": "workspace:"
+ },
+ "peerDependencies": {
+ "cross-fetch": "^3.1.5",
+ "next-auth": "workspace:"
+ },
+ "devDependencies": {
+ "@next-auth/adapter-test": "workspace:^0.0.0",
+ "@next-auth/tsconfig": "workspace:^0.0.0",
+ "jest": "^27.4.3",
+ "@types/jest": "^26.0.24"
+ },
+ "jest": {
+ "preset": "@next-auth/adapter-test/jest"
+ }
+}
diff --git a/packages/adapter-chiselstrike/src/index.ts b/packages/adapter-chiselstrike/src/index.ts
new file mode 100644
index 0000000000..7a90ca8b03
--- /dev/null
+++ b/packages/adapter-chiselstrike/src/index.ts
@@ -0,0 +1,191 @@
+import { fetch, Headers } from "cross-fetch";
+import { Account } from "next-auth";
+import type { Adapter, AdapterSession, AdapterUser, VerificationToken } from "next-auth/adapters"
+
+export class ChiselStrikeAdapter implements Adapter {
+ url: string; /// ChiselStrike backend.
+ secret: string;
+
+ /// Makes an adapter connected 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 fetch(), but adds a header with this.password.
+ public async secFetch(input: RequestInfo, init?: RequestInit | undefined): Promise {
+ init ??= {}
+ init.headers = new Headers(init.headers)
+ init.headers.set('ChiselAuth', this.secret)
+ return fetch(input, init)
+ }
+
+ createUser = async (user: Omit): Promise => {
+ const resp = await this.secFetch(this.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 this.secFetch(this.users(`/${id}`))
+ if (!resp.ok) { return null }
+ return userFromJson(await resp.json())
+ }
+
+ getUserByEmail = async (email: string): Promise => {
+ return userFromJson(await firstElementOrNull(await this.secFetch(this.users(`?${makeFilter({ email })}`))))
+ }
+
+ updateUser = async (user: Partial): Promise => {
+ // TODO: use PATCH
+ const get = await this.secFetch(this.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 this.secFetch(this.users(`/${user.id}`), { method: 'PUT', body })
+ await ensureOK(put, `writing user ${body}`)
+ return userFromJson(u)
+ }
+
+ getUserByAccount = async (providerAccountId: Pick): Promise => {
+ const acct = await firstElementOrNull(await this.secFetch(this.accounts(`?${makeFilter(providerAccountId)}`)))
+ if (!acct) { return null }
+ const uresp = await this.secFetch(this.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 this.secFetch(this.users(`/${userId}`), { method: 'DELETE' })
+ await ensureOK(resp, `deleting user ${userId}`)
+ resp = await this.secFetch(this.sessions(`?.userId=${userId}`), { method: 'DELETE' })
+ await ensureOK(resp, `deleting sessions for ${userId}`)
+ resp = await this.secFetch(this.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 this.secFetch(this.accounts(), { method: 'POST', body })
+ await ensureOK(resp, `posting account ${body}`);
+ return await resp.json()
+ }
+
+ // unlinkAccount?: ((providerAccountId: Pick) => Promise | Awaitable) | undefined;
+ unlinkAccount = async (providerAccountId: Pick): Promise => {
+ const resp = await this.secFetch(this.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 this.secFetch(this.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 firstElementOrNull(await this.secFetch(this.sessions(`?.sessionToken=${sessionToken}`)))
+ if (!session) { return null }
+ const rUser = await this.secFetch(`${this.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 firstElementOrNull(await this.secFetch(this.sessions(`?.sessionToken=${session.sessionToken}`)))
+ if (!dbSession) { return null }
+ Object.keys(session).forEach(key => dbSession[key] = (session as Record)[key])
+ const body = JSON.stringify(dbSession)
+ const put = await this.secFetch(this.sessions(`/${dbSession.id}`), { method: 'PUT', body })
+ await ensureOK(put, `writing user ${body}`)
+ return dbSession
+ }
+
+ // deleteSession: (sessionToken: string) => Promise | Awaitable;
+ deleteSession = async (sessionToken: string): Promise => {
+ const get = await this.secFetch(this.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 this.secFetch(this.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 firstElementOrNull(await this.secFetch(this.tokens(`?${makeFilter(params)}`)))
+ if (!token) { return null }
+ await this.secFetch(this.tokens(`/${token.id}`), { method: 'DELETE' })
+ return { identifier: token.identifier, token: token.token, expires: new Date(token.expires) }
+ }
+
+ async deleteEverything() {
+ await this.secFetch(this.sessions('?all=true'), { method: 'DELETE' })
+ await this.secFetch(this.accounts('?all=true'), { method: 'DELETE' })
+ await this.secFetch(this.users('?all=true'), { method: 'DELETE' })
+ await this.secFetch(this.tokens('?all=true'), { method: 'DELETE' })
+ }
+}
+
+function userFromJson(user: any): AdapterUser {
+ 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;
+ }
+}
+
+async function firstElementOrNull(resp: Response) {
+ if (!resp.ok) { return null }
+ const j = await resp.json()
+ if (!Array.isArray(j) || j.length < 1) { return null }
+ return j[0]
+}
+
+function sessionFromJson(session: any): AdapterSession {
+ 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..34eacc18d2
--- /dev/null
+++ b/packages/adapter-chiselstrike/tests/basic.test.ts
@@ -0,0 +1,48 @@
+import { runBasicTests } from "@next-auth/adapter-test"
+import { ChiselStrikeAdapter } from "../src"
+import fetch from "cross-fetch"
+
+const adapter = new ChiselStrikeAdapter('http://localhost:8080', '1234');
+
+runBasicTests(
+ {
+ adapter,
+ db: {
+ connect: async () => {
+ await adapter.deleteEverything()
+ },
+ session: async (sessionToken: string) => {
+ const s = await adFetch(adapter.sessions(`?.sessionToken=${sessionToken}`), `session ${sessionToken}`)
+ return s ? { ...s, expires: new Date(s.expires) } : null
+ },
+ user: async (id: string) => {
+ return await adapter.getUser(id)
+ },
+ account: async (providerAccountId: { provider: string; providerAccountId: string }) => {
+ const providerFilter = providerAccountId.provider ? `.provider=${providerAccountId.provider}` : ''
+ const providerAccountIdFilter =
+ providerAccountId.providerAccountId ? `.providerAccountId=${providerAccountId.providerAccountId}` : ''
+ return await adFetch(
+ adapter.accounts(`?${providerFilter}&${providerAccountIdFilter}`),
+ `account ${JSON.stringify(providerAccountId)}`)
+ },
+ verificationToken: async (params: { identifier: string; token: string }) => {
+ const idFilter = `.identifier=${params.identifier}`
+ const tokenFilter = `.token=${params.token}`
+ let token = await adFetch(
+ adapter.tokens(`?${idFilter}&${tokenFilter}`),
+ `Fetching token ${JSON.stringify(params)}`)
+ return token ? { ...token, expires: new Date(token.expires), id: undefined } : null
+ }
+ }
+ }
+)
+
+/// Fetches an object via adapter, performing common error-checking.
+async function adFetch(url: string, what: string): Promise {
+ const res = await adapter.secFetch(url);
+ if (!res.ok) { return null }
+ const jres = await res.json();
+ if (!Array.isArray(jres)) { throw new Error(`Fetch result for ${what}: ${JSON.stringify(jres)}`); }
+ return jres.length < 1 ? null : jres[0];
+}
\ 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..3198fabfb5
--- /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 fetch from "cross-fetch"
+import { Account } from "next-auth"
+import { ChiselStrikeAdapter } from "../src"
+export { }
+
+const a = new ChiselStrikeAdapter('http://localhost:8080', '1234')
+
+beforeEach(async () => {
+ await a.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()
+})
From 03fd7a57acaa838ca0418ae201488b12e25ea3b5 Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Thu, 19 May 2022 20:44:03 -0400
Subject: [PATCH 02/10] Make ChiselStrikeAdapter a function
https://github.com/nextauthjs/next-auth/pull/4573#pullrequestreview-978280321
Signed-off-by: Dejan Mircevski
---
packages/adapter-chiselstrike/README.md | 4 +-
packages/adapter-chiselstrike/src/index.ts | 243 +++++++++---------
.../adapter-chiselstrike/tests/basic.test.ts | 30 +--
.../adapter-chiselstrike/tests/custom.test.ts | 7 +-
4 files changed, 134 insertions(+), 150 deletions(-)
diff --git a/packages/adapter-chiselstrike/README.md b/packages/adapter-chiselstrike/README.md
index 13ad861d38..498d07a072 100644
--- a/packages/adapter-chiselstrike/README.md
+++ b/packages/adapter-chiselstrike/README.md
@@ -26,7 +26,7 @@ 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 = new CSAdapter(, )
+const adapter = ChiselStrikeAdapter(new ChiselStrikeAuthFetcher(, ))
export default NextAuth({
adapter,
@@ -64,7 +64,7 @@ Before running a build/test cycle, please set up a ChiselStrike backend, either
```json
{ "CHISELD_AUTH_SECRET" : "1234" }
```
-If running a ChiselStrike backend in the cloud, please edit all instances of `new ChiselStrikeAdapter` in the `tests/` directory to reflect your backend's URL and auth secret.
+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
diff --git a/packages/adapter-chiselstrike/src/index.ts b/packages/adapter-chiselstrike/src/index.ts
index 7a90ca8b03..3128840c8a 100644
--- a/packages/adapter-chiselstrike/src/index.ts
+++ b/packages/adapter-chiselstrike/src/index.ts
@@ -2,11 +2,11 @@ import { fetch, Headers } from "cross-fetch";
import { Account } from "next-auth";
import type { Adapter, AdapterSession, AdapterUser, VerificationToken } from "next-auth/adapters"
-export class ChiselStrikeAdapter implements Adapter {
+export class ChiselStrikeAuthFetcher {
url: string; /// ChiselStrike backend.
secret: string;
- /// Makes an adapter connected to the ChiselStrike backend at this URL. Use the secret provided by the ChiselStrike platform.
+ /// 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
@@ -33,132 +33,132 @@ export class ChiselStrikeAdapter implements Adapter {
return this.auth('tokens') + (suffix ?? '');
}
- /// Like fetch(), but adds a header with this.password.
- public async secFetch(input: RequestInfo, init?: RequestInit | undefined): Promise {
+ /// 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)
}
- createUser = async (user: Omit): Promise => {
- const resp = await this.secFetch(this.users(), { method: 'POST', body: JSON.stringify(user) })
- await ensureOK(resp, `posting user ${JSON.stringify(user)}`);
- return userFromJson(await resp.json())
+ /// 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) || jres.length < 1) { return null }
+ return jres[0]
}
- getUser = async (id: string): Promise => {
- const resp = await this.secFetch(this.users(`/${id}`))
- if (!resp.ok) { return null }
- return userFromJson(await resp.json())
- }
-
- getUserByEmail = async (email: string): Promise => {
- return userFromJson(await firstElementOrNull(await this.secFetch(this.users(`?${makeFilter({ email })}`))))
- }
-
- updateUser = async (user: Partial): Promise => {
- // TODO: use PATCH
- const get = await this.secFetch(this.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 this.secFetch(this.users(`/${user.id}`), { method: 'PUT', body })
- await ensureOK(put, `writing user ${body}`)
- return userFromJson(u)
- }
-
- getUserByAccount = async (providerAccountId: Pick): Promise => {
- const acct = await firstElementOrNull(await this.secFetch(this.accounts(`?${makeFilter(providerAccountId)}`)))
- if (!acct) { return null }
- const uresp = await this.secFetch(this.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 this.secFetch(this.users(`/${userId}`), { method: 'DELETE' })
- await ensureOK(resp, `deleting user ${userId}`)
- resp = await this.secFetch(this.sessions(`?.userId=${userId}`), { method: 'DELETE' })
- await ensureOK(resp, `deleting sessions for ${userId}`)
- resp = await this.secFetch(this.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 this.secFetch(this.accounts(), { method: 'POST', body })
- await ensureOK(resp, `posting account ${body}`);
- return await resp.json()
- }
-
- // unlinkAccount?: ((providerAccountId: Pick) => Promise | Awaitable) | undefined;
- unlinkAccount = async (providerAccountId: Pick): Promise => {
- const resp = await this.secFetch(this.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 this.secFetch(this.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 firstElementOrNull(await this.secFetch(this.sessions(`?.sessionToken=${sessionToken}`)))
- if (!session) { return null }
- const rUser = await this.secFetch(`${this.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 firstElementOrNull(await this.secFetch(this.sessions(`?.sessionToken=${session.sessionToken}`)))
- if (!dbSession) { return null }
- Object.keys(session).forEach(key => dbSession[key] = (session as Record)[key])
- const body = JSON.stringify(dbSession)
- const put = await this.secFetch(this.sessions(`/${dbSession.id}`), { method: 'PUT', body })
- await ensureOK(put, `writing user ${body}`)
- return dbSession
- }
-
- // deleteSession: (sessionToken: string) => Promise | Awaitable;
- deleteSession = async (sessionToken: string): Promise => {
- const get = await this.secFetch(this.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 this.secFetch(this.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 firstElementOrNull(await this.secFetch(this.tokens(`?${makeFilter(params)}`)))
- if (!token) { return null }
- await this.secFetch(this.tokens(`/${token.id}`), { method: 'DELETE' })
- return { identifier: token.identifier, token: token.token, expires: new Date(token.expires) }
+ public async deleteEverything() {
+ await this.fetch(this.sessions('?all=true'), { method: 'DELETE' })
+ await this.fetch(this.accounts('?all=true'), { method: 'DELETE' })
+ await this.fetch(this.users('?all=true'), { method: 'DELETE' })
+ await this.fetch(this.tokens('?all=true'), { method: 'DELETE' })
}
+}
- async deleteEverything() {
- await this.secFetch(this.sessions('?all=true'), { method: 'DELETE' })
- await this.secFetch(this.accounts('?all=true'), { method: 'DELETE' })
- await this.secFetch(this.users('?all=true'), { method: 'DELETE' })
- await this.secFetch(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()
+ },
+ // 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.keys(session).forEach(key => dbSession[key] = session[key])
+ 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) }
+ }
}
}
@@ -174,13 +174,6 @@ async function ensureOK(resp: Response, during: string) {
}
}
-async function firstElementOrNull(resp: Response) {
- if (!resp.ok) { return null }
- const j = await resp.json()
- if (!Array.isArray(j) || j.length < 1) { return null }
- return j[0]
-}
-
function sessionFromJson(session: any): AdapterSession {
return { ...session, expires: new Date(session.expires), id: session.id ?? 'impossible: fetched CSession has null ID' }
}
diff --git a/packages/adapter-chiselstrike/tests/basic.test.ts b/packages/adapter-chiselstrike/tests/basic.test.ts
index 34eacc18d2..aa8c6ff5ee 100644
--- a/packages/adapter-chiselstrike/tests/basic.test.ts
+++ b/packages/adapter-chiselstrike/tests/basic.test.ts
@@ -1,18 +1,19 @@
import { runBasicTests } from "@next-auth/adapter-test"
-import { ChiselStrikeAdapter } from "../src"
+import { ChiselStrikeAdapter, ChiselStrikeAuthFetcher } from "../src"
import fetch from "cross-fetch"
-const adapter = new ChiselStrikeAdapter('http://localhost:8080', '1234');
+const fetcher = new ChiselStrikeAuthFetcher('http://localhost:8080', '1234')
+const adapter = ChiselStrikeAdapter(fetcher)
runBasicTests(
{
adapter,
db: {
connect: async () => {
- await adapter.deleteEverything()
+ await fetcher.deleteEverything()
},
session: async (sessionToken: string) => {
- const s = await adFetch(adapter.sessions(`?.sessionToken=${sessionToken}`), `session ${sessionToken}`)
+ const s = await fetcher.filter(fetcher.sessions(`?.sessionToken=${sessionToken}`))
return s ? { ...s, expires: new Date(s.expires) } : null
},
user: async (id: string) => {
@@ -22,27 +23,16 @@ runBasicTests(
const providerFilter = providerAccountId.provider ? `.provider=${providerAccountId.provider}` : ''
const providerAccountIdFilter =
providerAccountId.providerAccountId ? `.providerAccountId=${providerAccountId.providerAccountId}` : ''
- return await adFetch(
- adapter.accounts(`?${providerFilter}&${providerAccountIdFilter}`),
- `account ${JSON.stringify(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 adFetch(
- adapter.tokens(`?${idFilter}&${tokenFilter}`),
- `Fetching token ${JSON.stringify(params)}`)
+ let token = await fetcher.filter(
+ fetcher.tokens(`?${idFilter}&${tokenFilter}`))
return token ? { ...token, expires: new Date(token.expires), id: undefined } : null
}
}
}
-)
-
-/// Fetches an object via adapter, performing common error-checking.
-async function adFetch(url: string, what: string): Promise {
- const res = await adapter.secFetch(url);
- if (!res.ok) { return null }
- const jres = await res.json();
- if (!Array.isArray(jres)) { throw new Error(`Fetch result for ${what}: ${JSON.stringify(jres)}`); }
- return jres.length < 1 ? null : jres[0];
-}
\ No newline at end of file
+)
\ No newline at end of file
diff --git a/packages/adapter-chiselstrike/tests/custom.test.ts b/packages/adapter-chiselstrike/tests/custom.test.ts
index 3198fabfb5..1926d84df0 100644
--- a/packages/adapter-chiselstrike/tests/custom.test.ts
+++ b/packages/adapter-chiselstrike/tests/custom.test.ts
@@ -9,13 +9,14 @@
import fetch from "cross-fetch"
import { Account } from "next-auth"
-import { ChiselStrikeAdapter } from "../src"
+import { ChiselStrikeAdapter, ChiselStrikeAuthFetcher } from "../src"
export { }
-const a = new ChiselStrikeAdapter('http://localhost:8080', '1234')
+const fetcher = new ChiselStrikeAuthFetcher('http://localhost:8080', '1234')
+const a = ChiselStrikeAdapter(fetcher)
beforeEach(async () => {
- await a.deleteEverything()
+ await fetcher.deleteEverything()
})
test('createUser returns payload', async () => {
From 5f089474d7a33dee463d08ad774c46ad6af59e78 Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Thu, 19 May 2022 23:12:11 -0400
Subject: [PATCH 03/10] Use node-fetch instead of cross-fetch
Signed-off-by: Dejan Mircevski
---
packages/adapter-chiselstrike/package.json | 8 ++++----
packages/adapter-chiselstrike/src/index.ts | 3 ++-
packages/adapter-chiselstrike/tests/basic.test.ts | 2 +-
packages/adapter-chiselstrike/tests/custom.test.ts | 2 +-
4 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/packages/adapter-chiselstrike/package.json b/packages/adapter-chiselstrike/package.json
index 5ed3f96720..96d9f46a12 100644
--- a/packages/adapter-chiselstrike/package.json
+++ b/packages/adapter-chiselstrike/package.json
@@ -28,12 +28,12 @@
"dist"
],
"dependencies": {
- "cross-fetch": "^3.1.5",
- "next-auth": "workspace:"
+ "next-auth": "workspace:",
+ "node-fetch": "^2.6.1"
},
"peerDependencies": {
- "cross-fetch": "^3.1.5",
- "next-auth": "workspace:"
+ "next-auth": "workspace:",
+ "node-fetch": "^2.6.1"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
diff --git a/packages/adapter-chiselstrike/src/index.ts b/packages/adapter-chiselstrike/src/index.ts
index 3128840c8a..f56d66d96b 100644
--- a/packages/adapter-chiselstrike/src/index.ts
+++ b/packages/adapter-chiselstrike/src/index.ts
@@ -1,6 +1,7 @@
-import { fetch, Headers } from "cross-fetch";
import { Account } from "next-auth";
import type { Adapter, AdapterSession, AdapterUser, VerificationToken } from "next-auth/adapters"
+import { Headers, RequestInfo, RequestInit, Response } from "node-fetch"
+import fetch from "node-fetch"
export class ChiselStrikeAuthFetcher {
url: string; /// ChiselStrike backend.
diff --git a/packages/adapter-chiselstrike/tests/basic.test.ts b/packages/adapter-chiselstrike/tests/basic.test.ts
index aa8c6ff5ee..5900fc009e 100644
--- a/packages/adapter-chiselstrike/tests/basic.test.ts
+++ b/packages/adapter-chiselstrike/tests/basic.test.ts
@@ -1,6 +1,6 @@
import { runBasicTests } from "@next-auth/adapter-test"
import { ChiselStrikeAdapter, ChiselStrikeAuthFetcher } from "../src"
-import fetch from "cross-fetch"
+import fetch from "node-fetch"
const fetcher = new ChiselStrikeAuthFetcher('http://localhost:8080', '1234')
const adapter = ChiselStrikeAdapter(fetcher)
diff --git a/packages/adapter-chiselstrike/tests/custom.test.ts b/packages/adapter-chiselstrike/tests/custom.test.ts
index 1926d84df0..9b3ef7797d 100644
--- a/packages/adapter-chiselstrike/tests/custom.test.ts
+++ b/packages/adapter-chiselstrike/tests/custom.test.ts
@@ -7,7 +7,7 @@
* Start `chiseld` in the directory of that .env file, then run `jest` here in this directory.
*/
-import fetch from "cross-fetch"
+import fetch from "node-fetch"
import { Account } from "next-auth"
import { ChiselStrikeAdapter, ChiselStrikeAuthFetcher } from "../src"
export { }
From 24a60b75f680aa40fa31bf9345ae9533a6f99d55 Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Thu, 23 Jun 2022 10:20:57 -0400
Subject: [PATCH 04/10] Update for ChiselStrike v0.10
Signed-off-by: Dejan Mircevski
---
packages/adapter-chiselstrike/package.json | 2 +-
packages/adapter-chiselstrike/src/index.ts | 14 ++++++++------
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/packages/adapter-chiselstrike/package.json b/packages/adapter-chiselstrike/package.json
index 96d9f46a12..329c78c1ea 100644
--- a/packages/adapter-chiselstrike/package.json
+++ b/packages/adapter-chiselstrike/package.json
@@ -1,6 +1,6 @@
{
"name": "@next-auth/chiselstrike-adapter",
- "version": "1.0.0",
+ "version": "0.10.0",
"description": "ChiselStrike adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
diff --git a/packages/adapter-chiselstrike/src/index.ts b/packages/adapter-chiselstrike/src/index.ts
index f56d66d96b..2b5368b910 100644
--- a/packages/adapter-chiselstrike/src/index.ts
+++ b/packages/adapter-chiselstrike/src/index.ts
@@ -44,11 +44,11 @@ export class ChiselStrikeAuthFetcher {
/// 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);
+ const res = await this.fetch(url)
if (!res.ok) { return null }
const jres = await res.json()
- if (!Array.isArray(jres) || jres.length < 1) { return null }
- return jres[0]
+ if (!Array.isArray(jres.results) || jres.results.length < 1) { return null }
+ return jres.results[0]
}
public async deleteEverything() {
@@ -107,7 +107,7 @@ export function ChiselStrikeAdapter(fetcher: ChiselStrikeAuthFetcher): Adapter {
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()
+ return (await resp.json()).results
},
// unlinkAccount?: ((providerAccountId: Pick) => Promise | Awaitable) | undefined;
unlinkAccount: async (providerAccountId: Pick): Promise => {
@@ -163,7 +163,8 @@ export function ChiselStrikeAdapter(fetcher: ChiselStrikeAuthFetcher): Adapter {
}
}
-function userFromJson(user: any): AdapterUser {
+function userFromJson(json: any): AdapterUser {
+ const user = json?.results ?? json
return user ? { ...user, emailVerified: new Date(user.emailVerified) } : null
}
@@ -175,7 +176,8 @@ async function ensureOK(resp: Response, during: string) {
}
}
-function sessionFromJson(session: any): AdapterSession {
+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' }
}
From ab20bf7984403f70d0b87929c90e9d7e79b8beb4 Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Thu, 23 Jun 2022 10:25:50 -0400
Subject: [PATCH 05/10] Fix type error in updateSession
Thanks @luisfvieirasilva !
Co-authored-by: luisfvieirasilva
---
packages/adapter-chiselstrike/src/index.ts | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/packages/adapter-chiselstrike/src/index.ts b/packages/adapter-chiselstrike/src/index.ts
index 2b5368b910..4bfa10326a 100644
--- a/packages/adapter-chiselstrike/src/index.ts
+++ b/packages/adapter-chiselstrike/src/index.ts
@@ -134,7 +134,13 @@ export function ChiselStrikeAdapter(fetcher: ChiselStrikeAuthFetcher): Adapter {
// TODO: use PATCH
const dbSession = await fetcher.filter(fetcher.sessions(`?.sessionToken=${session.sessionToken}`))
if (!dbSession) { return null }
- Object.keys(session).forEach(key => dbSession[key] = session[key])
+ 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}`)
From 125ca13f71b663aa21ec7c42173fd4fb9daf0e7f Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Fri, 24 Jun 2022 11:37:40 -0400
Subject: [PATCH 06/10] Add tsc build
Signed-off-by: Dejan Mircevski
---
packages/adapter-chiselstrike/package.json | 1 +
packages/adapter-chiselstrike/tsconfig.json | 9 +++++++++
2 files changed, 10 insertions(+)
create mode 100644 packages/adapter-chiselstrike/tsconfig.json
diff --git a/packages/adapter-chiselstrike/package.json b/packages/adapter-chiselstrike/package.json
index 329c78c1ea..4b54f8ec90 100644
--- a/packages/adapter-chiselstrike/package.json
+++ b/packages/adapter-chiselstrike/package.json
@@ -21,6 +21,7 @@
"access": "public"
},
"scripts": {
+ "build": "tsc",
"test": "jest"
},
"files": [
diff --git a/packages/adapter-chiselstrike/tsconfig.json b/packages/adapter-chiselstrike/tsconfig.json
new file mode 100644
index 0000000000..4a4889cf23
--- /dev/null
+++ b/packages/adapter-chiselstrike/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "@next-auth/tsconfig/adapters.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist",
+ },
+ "include": ["."],
+ "exclude": ["tests", "dist", "jest.config.js"]
+}
From 42903e164ae159b093e1d284483679c1e3f53f05 Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Fri, 24 Jun 2022 11:44:17 -0400
Subject: [PATCH 07/10] Remove fetch polyfill
We now only use `node-fetch` in tests.
Thanks to @barbieri for coding this.
Signed-off-by: Dejan Mircevski
---
packages/adapter-chiselstrike/jest.config.js | 5 +++++
packages/adapter-chiselstrike/package.json | 14 ++++----------
packages/adapter-chiselstrike/src/index.ts | 4 +---
packages/adapter-chiselstrike/tests/basic.test.ts | 1 -
packages/adapter-chiselstrike/tests/custom.test.ts | 1 -
packages/adapter-chiselstrike/tests/jest-setup.js | 3 +++
packages/adapter-chiselstrike/tsconfig.json | 3 +--
7 files changed, 14 insertions(+), 17 deletions(-)
create mode 100644 packages/adapter-chiselstrike/jest.config.js
create mode 100644 packages/adapter-chiselstrike/tests/jest-setup.js
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/package.json b/packages/adapter-chiselstrike/package.json
index 4b54f8ec90..87c25c5bb8 100644
--- a/packages/adapter-chiselstrike/package.json
+++ b/packages/adapter-chiselstrike/package.json
@@ -28,21 +28,15 @@
"README.md",
"dist"
],
- "dependencies": {
- "next-auth": "workspace:",
- "node-fetch": "^2.6.1"
- },
"peerDependencies": {
- "next-auth": "workspace:",
- "node-fetch": "^2.6.1"
+ "next-auth": "workspace:*"
},
"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",
- "@types/jest": "^26.0.24"
- },
- "jest": {
- "preset": "@next-auth/adapter-test/jest"
+ "next-auth": "workspace:*",
+ "node-fetch": "^2.6.1"
}
}
diff --git a/packages/adapter-chiselstrike/src/index.ts b/packages/adapter-chiselstrike/src/index.ts
index 4bfa10326a..72f049f4b3 100644
--- a/packages/adapter-chiselstrike/src/index.ts
+++ b/packages/adapter-chiselstrike/src/index.ts
@@ -1,7 +1,5 @@
-import { Account } from "next-auth";
+import type { Account } from "next-auth";
import type { Adapter, AdapterSession, AdapterUser, VerificationToken } from "next-auth/adapters"
-import { Headers, RequestInfo, RequestInit, Response } from "node-fetch"
-import fetch from "node-fetch"
export class ChiselStrikeAuthFetcher {
url: string; /// ChiselStrike backend.
diff --git a/packages/adapter-chiselstrike/tests/basic.test.ts b/packages/adapter-chiselstrike/tests/basic.test.ts
index 5900fc009e..d4b46b0042 100644
--- a/packages/adapter-chiselstrike/tests/basic.test.ts
+++ b/packages/adapter-chiselstrike/tests/basic.test.ts
@@ -1,6 +1,5 @@
import { runBasicTests } from "@next-auth/adapter-test"
import { ChiselStrikeAdapter, ChiselStrikeAuthFetcher } from "../src"
-import fetch from "node-fetch"
const fetcher = new ChiselStrikeAuthFetcher('http://localhost:8080', '1234')
const adapter = ChiselStrikeAdapter(fetcher)
diff --git a/packages/adapter-chiselstrike/tests/custom.test.ts b/packages/adapter-chiselstrike/tests/custom.test.ts
index 9b3ef7797d..4bc78d5ca5 100644
--- a/packages/adapter-chiselstrike/tests/custom.test.ts
+++ b/packages/adapter-chiselstrike/tests/custom.test.ts
@@ -7,7 +7,6 @@
* Start `chiseld` in the directory of that .env file, then run `jest` here in this directory.
*/
-import fetch from "node-fetch"
import { Account } from "next-auth"
import { ChiselStrikeAdapter, ChiselStrikeAuthFetcher } from "../src"
export { }
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
index 4a4889cf23..bf0a7cabce 100644
--- a/packages/adapter-chiselstrike/tsconfig.json
+++ b/packages/adapter-chiselstrike/tsconfig.json
@@ -2,8 +2,7 @@
"extends": "@next-auth/tsconfig/adapters.json",
"compilerOptions": {
"rootDir": "src",
- "outDir": "dist",
+ "outDir": "dist"
},
- "include": ["."],
"exclude": ["tests", "dist", "jest.config.js"]
}
From 98841a0c493cebd927f9daef676afdc1b555ecc6 Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Mon, 27 Jun 2022 11:09:32 -0400
Subject: [PATCH 08/10] Use a specific next-auth version in peerDependencies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Balázs Orbán
---
packages/adapter-chiselstrike/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/adapter-chiselstrike/package.json b/packages/adapter-chiselstrike/package.json
index 87c25c5bb8..ef0f6d0d85 100644
--- a/packages/adapter-chiselstrike/package.json
+++ b/packages/adapter-chiselstrike/package.json
@@ -29,7 +29,7 @@
"dist"
],
"peerDependencies": {
- "next-auth": "workspace:*"
+ "next-auth": "^4.6.0"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
From 58243aa3c3bdd31e2af0a5b608b66e9075069bfd Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Mon, 27 Jun 2022 12:06:32 -0400
Subject: [PATCH 09/10] Use Promise.all instead of four awaits
Signed-off-by: Dejan Mircevski
---
packages/adapter-chiselstrike/src/index.ts | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/packages/adapter-chiselstrike/src/index.ts b/packages/adapter-chiselstrike/src/index.ts
index 72f049f4b3..503e3f4e1f 100644
--- a/packages/adapter-chiselstrike/src/index.ts
+++ b/packages/adapter-chiselstrike/src/index.ts
@@ -50,10 +50,12 @@ export class ChiselStrikeAuthFetcher {
}
public async deleteEverything() {
- await this.fetch(this.sessions('?all=true'), { method: 'DELETE' })
- await this.fetch(this.accounts('?all=true'), { method: 'DELETE' })
- await this.fetch(this.users('?all=true'), { method: 'DELETE' })
- await this.fetch(this.tokens('?all=true'), { method: 'DELETE' })
+ 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' })
+ ])
}
}
@@ -138,7 +140,7 @@ export function ChiselStrikeAdapter(fetcher: ChiselStrikeAuthFetcher): Adapter {
} 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}`)
From 9b00fcdcc3c0bda5a5e13dc24deba42706c2c969 Mon Sep 17 00:00:00 2001
From: Dejan Mircevski
Date: Mon, 27 Jun 2022 13:00:02 -0400
Subject: [PATCH 10/10] Get DB user from the DB, not adapter
Signed-off-by: Dejan Mircevski
---
packages/adapter-chiselstrike/tests/basic.test.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/adapter-chiselstrike/tests/basic.test.ts b/packages/adapter-chiselstrike/tests/basic.test.ts
index d4b46b0042..df21cf0f08 100644
--- a/packages/adapter-chiselstrike/tests/basic.test.ts
+++ b/packages/adapter-chiselstrike/tests/basic.test.ts
@@ -16,7 +16,10 @@ runBasicTests(
return s ? { ...s, expires: new Date(s.expires) } : null
},
user: async (id: string) => {
- return await adapter.getUser(id)
+ 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}` : ''