Compare commits

..

4 Commits

Author SHA1 Message Date
GitHub Actions
0f4d43e9ad chore(release): bump package version(s) [skip ci] 2023-02-05 13:52:31 +00:00
Thang Vu
28583b8ab0 feat: drop crypto dependency, convert to ESM (#6603)
Co-authored-by: Balázs Orbán <info@balazsorban.com>

BREAKING CHANGE:
- This package now only ships ESM, as all maintained Node.js versions have native support
- Dropped the `crypto` Node.js import in favor of `uuid`. When `globalThis.crypto` is the default in the future, we can remove `uuid` again
2023-02-05 14:44:33 +01:00
Balázs Orbán
1e7538a955 fix: publish .d.ts.map, add experimental warning 2023-02-05 14:42:07 +01:00
Wyatt Ades
4258857e52 feat(adapters): move to firebase-admin in Firebase Adapter (#6225)
Co-authored-by: Balázs Orbán <info@balazsorban.com>
fixes https://github.com/nextauthjs/next-auth/issues/5049
closes https://github.com/nextauthjs/next-auth/pull/6230
closes https://github.com/nextauthjs/next-auth/pull/5449
closes https://github.com/nextauthjs/next-auth/pull/5055
fixes https://github.com/nextauthjs/next-auth/issues/4927

BREAKING CHANGE:
The adapter now expects `firebase-admin` instead of the `firebase` package and also supports either passing a config object or a firestore instance.
2023-02-05 14:41:20 +01:00
9 changed files with 131 additions and 103 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "@next-auth/dynamodb-adapter", "name": "@next-auth/dynamodb-adapter",
"repository": "https://github.com/nextauthjs/next-auth", "repository": "https://github.com/nextauthjs/next-auth",
"version": "1.2.0", "version": "3.0.0",
"description": "AWS DynamoDB adapter for next-auth.", "description": "AWS DynamoDB adapter for next-auth.",
"keywords": [ "keywords": [
"next-auth", "next-auth",
@@ -9,11 +9,18 @@
"oauth", "oauth",
"dynamodb" "dynamodb"
], ],
"type": "module",
"types": "./index.d.ts",
"homepage": "https://authjs.dev", "homepage": "https://authjs.dev",
"bugs": { "bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues" "url": "https://github.com/nextauthjs/next-auth/issues"
}, },
"main": "dist/index.js", "exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
}
},
"private": false, "private": false,
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@@ -26,7 +33,10 @@
}, },
"files": [ "files": [
"README.md", "README.md",
"dist" "index.js",
"index.d.ts",
"index.d.ts.map",
"src"
], ],
"author": "Pol Marnette", "author": "Pol Marnette",
"license": "ISC", "license": "ISC",
@@ -41,7 +51,11 @@
"@next-auth/adapter-test": "workspace:*", "@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*", "@next-auth/tsconfig": "workspace:*",
"@shelf/jest-dynamodb": "^2.1.0", "@shelf/jest-dynamodb": "^2.1.0",
"@types/uuid": "^9.0.0",
"jest": "^27.4.3", "jest": "^27.4.3",
"next-auth": "workspace:*" "next-auth": "workspace:*"
},
"dependencies": {
"uuid": "^9.0.0"
} }
} }

View File

@@ -1,4 +1,4 @@
import { randomBytes } from "crypto" import { v4 as uuid } from "uuid"
import type { import type {
BatchWriteCommandInput, BatchWriteCommandInput,
@@ -12,16 +12,12 @@ import type {
VerificationToken, VerificationToken,
} from "next-auth/adapters" } from "next-auth/adapters"
import { format, generateUpdateExpression } from "./utils"
export { format, generateUpdateExpression }
export interface DynamoDBAdapterOptions { export interface DynamoDBAdapterOptions {
tableName?: string, tableName?: string
partitionKey?: string, partitionKey?: string
sortKey?: string, sortKey?: string
indexName?: string, indexName?: string
indexPartitionKey?: string, indexPartitionKey?: string
indexSortKey?: string indexSortKey?: string
} }
@@ -30,17 +26,17 @@ export function DynamoDBAdapter(
options?: DynamoDBAdapterOptions options?: DynamoDBAdapterOptions
): Adapter { ): Adapter {
const TableName = options?.tableName ?? "next-auth" const TableName = options?.tableName ?? "next-auth"
const pk = options?.partitionKey ?? 'pk' const pk = options?.partitionKey ?? "pk"
const sk = options?.sortKey ?? 'sk' const sk = options?.sortKey ?? "sk"
const IndexName = options?.indexName ?? 'GSI1' const IndexName = options?.indexName ?? "GSI1"
const GSI1PK = options?.indexPartitionKey ?? 'GSI1PK' const GSI1PK = options?.indexPartitionKey ?? "GSI1PK"
const GSI1SK = options?.indexSortKey ?? 'GSI1SK' const GSI1SK = options?.indexSortKey ?? "GSI1SK"
return { return {
async createUser(data) { async createUser(data) {
const user: AdapterUser = { const user: AdapterUser = {
...(data as any), ...(data as any),
id: randomBytes(16).toString("hex"), id: uuid(),
} }
await client.put({ await client.put({
@@ -50,8 +46,8 @@ export function DynamoDBAdapter(
[pk]: `USER#${user.id}`, [pk]: `USER#${user.id}`,
[sk]: `USER#${user.id}`, [sk]: `USER#${user.id}`,
type: "USER", type: "USER",
[GSI1PK]: `USER#${user.email as string}`, [GSI1PK]: `USER#${user.email}`,
[GSI1SK]: `USER#${user.email as string}`, [GSI1SK]: `USER#${user.email}`,
}), }),
}) })
@@ -165,7 +161,7 @@ export function DynamoDBAdapter(
async linkAccount(data) { async linkAccount(data) {
const item = { const item = {
...data, ...data,
id: randomBytes(16).toString("hex"), id: uuid(),
[pk]: `USER#${data.userId}`, [pk]: `USER#${data.userId}`,
[sk]: `ACCOUNT#${data.provider}#${data.providerAccountId}`, [sk]: `ACCOUNT#${data.provider}#${data.providerAccountId}`,
[GSI1PK]: `ACCOUNT#${data.provider}`, [GSI1PK]: `ACCOUNT#${data.provider}`,
@@ -229,7 +225,7 @@ export function DynamoDBAdapter(
}, },
async createSession(data) { async createSession(data) {
const session = { const session = {
id: randomBytes(16).toString("hex"), id: uuid(),
...data, ...data,
} }
await client.put({ await client.put({
@@ -327,3 +323,73 @@ export function DynamoDBAdapter(
}, },
} }
} }
// https://github.com/honeinc/is-iso-date/blob/master/index.js
const isoDateRE =
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
function isDate(value: any) {
return value && isoDateRE.test(value) && !isNaN(Date.parse(value))
}
const format = {
/** Takes a plain old JavaScript object and turns it into a Dynamodb object */
to(object: Record<string, any>) {
const newObject: Record<string, unknown> = {}
for (const key in object) {
const value = object[key]
if (value instanceof Date) {
// DynamoDB requires the TTL attribute be a UNIX timestamp (in secs).
if (key === "expires") newObject[key] = value.getTime() / 1000
else newObject[key] = value.toISOString()
} else newObject[key] = value
}
return newObject
},
/** Takes a Dynamo object and returns a plain old JavaScript object */
from<T = Record<string, unknown>>(object?: Record<string, any>): T | null {
if (!object) return null
const newObject: Record<string, unknown> = {}
for (const key in object) {
// Filter DynamoDB specific attributes so it doesn't get passed to core,
// to avoid revealing the type of database
if (["pk", "sk", "GSI1PK", "GSI1SK"].includes(key)) continue
const value = object[key]
if (isDate(value)) newObject[key] = new Date(value)
// hack to keep type property in account
else if (key === "type" && ["SESSION", "VT", "USER"].includes(value))
continue
// The expires property is stored as a UNIX timestamp in seconds, but
// JavaScript needs it in milliseconds, so multiply by 1000.
else if (key === "expires" && typeof value === "number")
newObject[key] = new Date(value * 1000)
else newObject[key] = value
}
return newObject as T
},
}
function generateUpdateExpression(object: Record<string, any>): {
UpdateExpression: string
ExpressionAttributeNames: Record<string, string>
ExpressionAttributeValues: Record<string, unknown>
} {
const formatedSession = format.to(object)
let UpdateExpression = "set"
const ExpressionAttributeNames: Record<string, string> = {}
const ExpressionAttributeValues: Record<string, unknown> = {}
for (const property in formatedSession) {
UpdateExpression += ` #${property} = :${property},`
ExpressionAttributeNames["#" + property] = property
ExpressionAttributeValues[":" + property] = formatedSession[property]
}
UpdateExpression = UpdateExpression.slice(0, -1)
return {
UpdateExpression,
ExpressionAttributeNames,
ExpressionAttributeValues,
}
}
export { format, generateUpdateExpression }

View File

@@ -1,67 +0,0 @@
// https://github.com/honeinc/is-iso-date/blob/master/index.js
const isoDateRE =
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
function isDate(value: any) {
return value && isoDateRE.test(value) && !isNaN(Date.parse(value))
}
export const format = {
/** Takes a plain old JavaScript object and turns it into a Dynamodb object */
to(object: Record<string, any>) {
const newObject: Record<string, unknown> = {}
for (const key in object) {
const value = object[key]
if (value instanceof Date) {
// DynamoDB requires the TTL attribute be a UNIX timestamp (in secs).
if (key === "expires") newObject[key] = value.getTime() / 1000
else newObject[key] = value.toISOString()
} else newObject[key] = value
}
return newObject
},
/** Takes a Dynamo object and returns a plain old JavaScript object */
from<T = Record<string, unknown>>(object?: Record<string, any>): T | null {
if (!object) return null
const newObject: Record<string, unknown> = {}
for (const key in object) {
// Filter DynamoDB specific attributes so it doesn't get passed to core,
// to avoid revealing the type of database
if (["pk", "sk", "GSI1PK", "GSI1SK"].includes(key)) continue
const value = object[key]
if (isDate(value)) newObject[key] = new Date(value)
// hack to keep type property in account
else if (key === "type" && ["SESSION", "VT", "USER"].includes(value))
continue
// The expires property is stored as a UNIX timestamp in seconds, but
// JavaScript needs it in milliseconds, so multiply by 1000.
else if (key === "expires" && typeof value === "number")
newObject[key] = new Date(value * 1000)
else newObject[key] = value
}
return newObject as T
},
}
export function generateUpdateExpression(object: Record<string, any>): {
UpdateExpression: string
ExpressionAttributeNames: Record<string, string>
ExpressionAttributeValues: Record<string, unknown>
} {
const formatedSession = format.to(object)
let UpdateExpression = "set"
const ExpressionAttributeNames: Record<string, string> = {}
const ExpressionAttributeValues: Record<string, unknown> = {}
for (const property in formatedSession) {
UpdateExpression += ` #${property} = :${property},`
ExpressionAttributeNames["#" + property] = property
ExpressionAttributeValues[":" + property] = formatedSession[property]
}
UpdateExpression = UpdateExpression.slice(0, -1)
return {
UpdateExpression,
ExpressionAttributeNames,
ExpressionAttributeValues,
}
}

View File

@@ -1,4 +1,4 @@
import { format } from "../src/utils" import { format } from "../src/"
describe("dynamodb utils.format", () => { describe("dynamodb utils.format", () => {
it("format.to() preserves non-Date non-expires properties", () => { it("format.to() preserves non-Date non-expires properties", () => {

View File

@@ -2,7 +2,15 @@
"extends": "@next-auth/tsconfig/tsconfig.adapters.json", "extends": "@next-auth/tsconfig/tsconfig.adapters.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "src", "rootDir": "src",
"outDir": "dist" "outDir": ".",
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"skipDefaultLibCheck": true,
"strictNullChecks": true,
"stripInternal": true,
"declarationMap": true,
"declaration": true
}, },
"exclude": ["tests", "dist", "jest.config.js", "jest-dynamodb-config.js"] "exclude": ["tests", "dist", "jest.config.js", "jest-dynamodb-config.js"]
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@next-auth/firebase-adapter", "name": "@next-auth/firebase-adapter",
"version": "1.0.3", "version": "2.0.0",
"description": "Firebase adapter for next-auth.", "description": "Firebase adapter for next-auth.",
"homepage": "https://authjs.dev", "homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth", "repository": "https://github.com/nextauthjs/next-auth",
@@ -52,4 +52,4 @@
"jest": "^29.3.1", "jest": "^29.3.1",
"next-auth": "workspace:*" "next-auth": "workspace:*"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@auth/core", "name": "@auth/core",
"version": "0.3.0", "version": "0.4.0",
"description": "Authentication for the Web.", "description": "Authentication for the Web.",
"keywords": [ "keywords": [
"authentication", "authentication",
@@ -27,7 +27,7 @@
"types": "./index.d.ts", "types": "./index.d.ts",
"files": [ "files": [
"*.js", "*.js",
"*.d.ts", "*.d.ts*",
"lib", "lib",
"providers", "providers",
"src" "src"
@@ -93,4 +93,4 @@
"postcss": "8.4.19", "postcss": "8.4.19",
"postcss-nested": "6.0.0" "postcss-nested": "6.0.0"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@auth/sveltekit", "name": "@auth/sveltekit",
"version": "0.2.0", "version": "0.2.1",
"description": "Authentication for SvelteKit.", "description": "Authentication for SvelteKit.",
"keywords": [ "keywords": [
"authentication", "authentication",

17
pnpm-lock.yaml generated
View File

@@ -243,14 +243,19 @@ importers:
'@next-auth/adapter-test': workspace:* '@next-auth/adapter-test': workspace:*
'@next-auth/tsconfig': workspace:* '@next-auth/tsconfig': workspace:*
'@shelf/jest-dynamodb': ^2.1.0 '@shelf/jest-dynamodb': ^2.1.0
'@types/uuid': ^9.0.0
jest: ^27.4.3 jest: ^27.4.3
next-auth: workspace:* next-auth: workspace:*
uuid: ^9.0.0
dependencies:
uuid: 9.0.0
devDependencies: devDependencies:
'@aws-sdk/client-dynamodb': 3.113.0 '@aws-sdk/client-dynamodb': 3.113.0
'@aws-sdk/lib-dynamodb': 3.113.0_eb2z3hhrjl3qvyc6ecmpo2nhva '@aws-sdk/lib-dynamodb': 3.113.0_eb2z3hhrjl3qvyc6ecmpo2nhva
'@next-auth/adapter-test': link:../adapter-test '@next-auth/adapter-test': link:../adapter-test
'@next-auth/tsconfig': link:../tsconfig '@next-auth/tsconfig': link:../tsconfig
'@shelf/jest-dynamodb': 2.2.4_qsruu6yolbxs4rh6ixjhkibvwu '@shelf/jest-dynamodb': 2.2.4_qsruu6yolbxs4rh6ixjhkibvwu
'@types/uuid': 9.0.0
jest: 27.5.1 jest: 27.5.1
next-auth: link:../next-auth next-auth: link:../next-auth
@@ -12750,6 +12755,10 @@ packages:
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
dev: true dev: true
/@types/uuid/9.0.0:
resolution: {integrity: sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==}
dev: true
/@types/validator/13.7.3: /@types/validator/13.7.3:
resolution: {integrity: sha512-DNviAE5OUcZ5s+XEQHRhERLg8fOp8gSgvyJ4aaFASx5wwaObm+PBwTIMXiOFm1QrSee5oYwEAYb7LMzX2O88gA==} resolution: {integrity: sha512-DNviAE5OUcZ5s+XEQHRhERLg8fOp8gSgvyJ4aaFASx5wwaObm+PBwTIMXiOFm1QrSee5oYwEAYb7LMzX2O88gA==}
dev: true dev: true
@@ -13741,10 +13750,8 @@ packages:
indent-string: 4.0.0 indent-string: 4.0.0
dev: true dev: true
/ajv-formats/2.1.1_ajv@8.11.0: /ajv-formats/2.1.1:
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta: peerDependenciesMeta:
ajv: ajv:
optional: true optional: true
@@ -19535,7 +19542,7 @@ packages:
dependencies: dependencies:
'@apidevtools/json-schema-ref-parser': 9.0.9 '@apidevtools/json-schema-ref-parser': 9.0.9
ajv: 8.11.0 ajv: 8.11.0
ajv-formats: 2.1.1_ajv@8.11.0 ajv-formats: 2.1.1
body-parser: 1.20.0 body-parser: 1.20.0
content-type: 1.0.4 content-type: 1.0.4
deep-freeze: 0.0.1 deep-freeze: 0.0.1
@@ -30975,7 +30982,7 @@ packages:
dependencies: dependencies:
'@types/json-schema': 7.0.11 '@types/json-schema': 7.0.11
ajv: 8.11.0 ajv: 8.11.0
ajv-formats: 2.1.1_ajv@8.11.0 ajv-formats: 2.1.1
ajv-keywords: 5.1.0_ajv@8.11.0 ajv-keywords: 5.1.0_ajv@8.11.0
dev: true dev: true