1
0
mirror of synced 2026-02-05 15:00:09 -05:00

Compare commits

...

24 Commits

Author SHA1 Message Date
Siddharth Suresh
95617216f7 Merge branch 'main' into siddharth/authjs 2023-08-17 13:29:55 +05:30
Siddharth Suresh
e40872efbb feat: strongly type provider 2023-08-17 02:05:03 +05:30
Siddharth Suresh
f76eab6bf6 fix build 2023-08-17 01:51:37 +05:30
Siddharth Suresh
8c7c7a8055 Merge branch 'main' into siddharth/authjs 2023-08-14 22:05:50 +05:30
Siddharth Suresh
61d03b6724 Merge branch 'main' into siddharth/authjs 2023-08-14 21:52:29 +05:30
Siddharth Suresh
33c1252b62 pnpm lock fix 2023-08-14 20:12:51 +05:30
Siddharth Suresh
b55cbcfa65 Apply suggestions from code review 2023-08-14 19:38:58 +05:30
Siddharth Suresh
875b34fcb8 Merge branch 'siddharth/fix-ci-v2' into siddharth/authjs 2023-08-14 19:33:36 +05:30
Siddharth Suresh
47d10c8595 remove workspace from package.json 2023-08-14 19:16:16 +05:30
Siddharth Suresh
5a88801e75 fix: add overriding gitignore to mark changes in src 2023-07-27 19:44:08 +05:30
Siddharth Suresh
cae963ec7d decouple adapters from blitz-auth 2023-07-27 16:08:45 +05:30
Siddharth Suresh
6544d052bd change prisma command 2023-07-27 15:40:23 +05:30
Siddharth Suresh
857ede3445 add default port 3000 for tests 2023-07-27 15:33:12 +05:30
Siddharth Suresh
3d8a8d87ae update patch to include email provider support 2023-07-27 12:22:52 +05:30
Siddharth Suresh
5a52fa76d9 Merge branch 'siddharth/authjs' of https://github.com/blitz-js/blitz into siddharth/authjs 2023-07-27 12:13:07 +05:30
Siddharth Suresh
9a6525fff7 cleanuo 2023-07-27 12:12:42 +05:30
Siddharth Suresh
87afb00289 Create polite-papayas-joke.md 2023-07-27 11:55:20 +05:30
Siddharth Suresh
8ad4dfd0eb cleanup 2023-07-27 11:50:29 +05:30
Siddharth Suresh
9ee73fe3d5 consistant code 2023-07-27 11:49:48 +05:30
Siddharth Suresh
5bd03a7dfa add authjs email provider support 2023-07-27 11:44:28 +05:30
Siddharth Suresh
5ab010f289 fix types 2023-07-27 11:16:12 +05:30
Siddharth Suresh
bd3694a103 final fixes 2023-07-27 11:01:20 +05:30
Siddharth Suresh
2dc401436d patch authjs 2023-07-27 01:24:53 +05:30
Siddharth Suresh
4b23aa4f8b migrate to @auth/core 2023-07-26 23:06:56 +05:30
47 changed files with 295 additions and 361 deletions

View File

@@ -0,0 +1,6 @@
---
"@blitzjs/auth": major
"blitz": patch
---
Migrate `next-auth` adapter to use `@auth/core`

View File

@@ -1,4 +1,3 @@
const { withNextAuthAdapter } = require("@blitzjs/auth")
const { withBlitz } = require("@blitzjs/next")
/**
@@ -11,4 +10,4 @@ const config = {
},
}
module.exports = withBlitz(withNextAuthAdapter(config))
module.exports = withBlitz(config)

View File

@@ -24,6 +24,7 @@
]
},
"dependencies": {
"@auth/core": "0.10.0",
"@blitzjs/auth": "2.0.0-beta.31",
"@blitzjs/config": "2.0.0-beta.31",
"@blitzjs/next": "2.0.0-beta.31",
@@ -33,7 +34,6 @@
"@prisma/client": "4.6.1",
"blitz": "2.0.0-beta.31",
"next": "13.3.0",
"next-auth": "4.18.7",
"prisma": "4.6.1",
"react": "18.2.0",
"react-dom": "18.2.0",

View File

@@ -1,6 +1,6 @@
import { api } from "src/blitz-server"
import GithubProvider from "next-auth/providers/github"
import { NextAuthAdapter, BlitzNextAuthOptions } from "@blitzjs/auth/next-auth"
import GithubProvider from "@auth/core/providers/github"
import { AuthAdapter } from "@blitzjs/auth/adapters/authjs"
import db, { User } from "db"
import { Role } from "types"
@@ -13,20 +13,23 @@ const providers = [
]
export default api(
NextAuthAdapter({
AuthAdapter({
successRedirectUrl: "/",
errorRedirectUrl: "/error",
providers,
trustHost: true,
secret: process.env.AUTH_SECRET,
callback: async (user, account, profile, session) => {
console.log("USER SIDE PROFILE_DATA", { user, account, profile })
let newUser: User
if (!user) throw new Error("No user found")
try {
newUser = await db.user.findFirstOrThrow({ where: { name: { equals: user.name } } })
} catch (e) {
newUser = await db.user.create({
data: {
email: user.email as string,
name: user.name as string,
email: user.email ?? "",
name: user.name ?? "",
role: "USER",
},
})

View File

@@ -5,7 +5,6 @@ import { useCurrentUser } from "src/users/hooks/useCurrentUser"
import logout from "src/auth/mutations/logout"
import { useMutation } from "@blitzjs/rpc"
import { BlitzPage } from "@blitzjs/next"
import { Routes } from ".blitz"
import styles from "src/styles/Home.module.css"
/*
@@ -32,6 +31,8 @@ const UserInfo = () => {
User id: <code>{currentUser.id}</code>
<br />
User role: <code>{currentUser.role}</code>
<br />
User email: <code>{currentUser.email}</code>
</div>
</>
)
@@ -44,11 +45,11 @@ const UserInfo = () => {
<Link href={"/login"} className={styles.loginButton}>
<strong>Login</strong>
</Link>
<Link href="/api/auth/github/login" passHref legacyBehavior>
<a className="button small">
<a href="/api/auth/github/signin">
<p className="button small">
<strong>Sign in with GitHub</strong>
</a>
</Link>
</p>
</a>
</>
)
}
@@ -79,8 +80,6 @@ const Home: BlitzPage = () => {
<h1>Your database & authentication is ready. Try it by signing up.</h1>
{/* Auth */}
<div className={styles.buttonContainer}>
<Suspense fallback="Loading...">
<UserInfo />

View File

@@ -2,6 +2,7 @@ import { useQuery } from "@blitzjs/rpc"
import getCurrentUser from "src/users/queries/getCurrentUser"
export const useCurrentUser = () => {
console.log("useCurrentUser")
const [user] = useQuery(getCurrentUser, null)
return user
}

View File

@@ -11,7 +11,7 @@ import {
import webdriver from "../../utils/next-webdriver"
let app: any
let appPort: number
let appPort: number = 3000
const runTests = () => {
describe("Auth", () => {
@@ -263,7 +263,7 @@ describe("Auth Tests", () => {
describe("dev mode", () => {
beforeAll(async () => {
try {
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
appPort = await findPort()
app = await blitzLaunchApp(appPort, {cwd: process.cwd()})
} catch (error) {

View File

@@ -14,7 +14,7 @@ import fetch from "node-fetch"
import {fromBase64} from "b64-lite"
let app: any
let appPort: number
let appPort: number = 3000
const HEADER_CSRF = "anti-csrf"
const COOKIE_PUBLIC_DATA_TOKEN = "auth-tests-cookie-prefix_sPublicDataToken"
const COOKIE_SESSION_TOKEN = "auth-tests-cookie-prefix_sSessionToken"
@@ -132,7 +132,7 @@ describe("Auth Tests", () => {
describe("dev mode", async () => {
beforeAll(async () => {
try {
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
appPort = await findPort()
app = await blitzLaunchApp(appPort, {cwd: process.cwd()})
} catch (error) {

View File

@@ -15,7 +15,7 @@ import webdriver from "../../utils/next-webdriver"
import {join} from "path"
let app: any
let appPort: number
let appPort: number = 3000
const appDir = join(__dirname, "../")
const runTests = (mode?: string) => {
@@ -50,7 +50,7 @@ describe("getInitialProps Tests", () => {
describe("dev mode", () => {
beforeAll(async () => {
try {
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
appPort = await findPort()
app = await blitzLaunchApp(appPort, {cwd: process.cwd()})
} catch (error) {

View File

@@ -13,7 +13,7 @@ import {
import {join} from "path"
let app: any
let appPort: number
let appPort: number = 3000
const appDir = join(__dirname, "../")
const runTests = () => {

View File

@@ -7,7 +7,7 @@ import fetch from "node-fetch"
import {fromBase64} from "b64-lite"
let app: any
let appPort: number
let appPort: number = 3000
const HEADER_CSRF = "anti-csrf"
const COOKIE_PUBLIC_DATA_TOKEN = "auth-tests-cookie-prefix_sPublicDataToken"
const COOKIE_SESSION_TOKEN = "auth-tests-cookie-prefix_sSessionToken"
@@ -156,7 +156,7 @@ describe("Auth Tests", () => {
describe("dev mode", async () => {
beforeAll(async () => {
try {
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
appPort = await findPort()
app = await blitzLaunchApp(appPort, {cwd: process.cwd()})
} catch (error) {

View File

@@ -15,7 +15,7 @@ import webdriver from "../../utils/next-webdriver"
import {join} from "path"
let app: any
let appPort: number
let appPort: number = 3000
const appDir = join(__dirname, "../")
const runTests = (mode?: string) => {
@@ -40,7 +40,7 @@ describe("No Suspense Tests", () => {
describe("dev mode", () => {
beforeAll(async () => {
try {
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
appPort = await findPort()
app = await blitzLaunchApp(appPort, {cwd: process.cwd()})
} catch (error) {

View File

@@ -10,7 +10,7 @@ import {
import webdriver from "../../utils/next-webdriver"
let app: any
let appPort: number
let appPort: number = 3000
const runTests = () => {
describe("get query data", () => {
@@ -114,7 +114,7 @@ describe("React Query Utils Tests", () => {
describe("dev mode", () => {
beforeAll(async () => {
try {
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
appPort = await findPort()
app = await blitzLaunchApp(appPort, {cwd: process.cwd()})
} catch (error) {

View File

@@ -15,7 +15,7 @@ import webdriver from "../../utils/next-webdriver"
import {join} from "path"
let app: any
let appPort: number
let appPort: number = 3000
const appDir = join(__dirname, "../")
const runTests = (mode?: string) => {
@@ -39,7 +39,7 @@ describe("Trailing Slash Tests", () => {
describe("dev mode", () => {
beforeAll(async () => {
try {
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
appPort = await findPort()
app = await blitzLaunchApp(appPort, {cwd: process.cwd()})
} catch (error) {

View File

@@ -44,5 +44,10 @@
"ignoredRules": [
"EXTERNAL_MISMATCH"
]
},
"pnpm": {
"patchedDependencies": {
"@auth/core@0.10.0": "patches/@auth__core@0.10.0.patch"
}
}
}

1
packages/blitz-auth/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
adapters

View File

@@ -5,7 +5,11 @@ const config: BuildConfig = {
"./src/index-browser",
"./src/index-server",
"./src/server/secure-password",
"./src/server/adapters/next-auth",
{
builder: "mkdist",
input: "./src/adapters/",
outDir: "./adapters",
},
],
externals: ["index-browser.cjs", "index-browser.mjs", "react"],
declaration: true,

View File

@@ -1 +0,0 @@
export * from "./dist/next-auth"

View File

@@ -1 +0,0 @@
module.exports = require("./dist/next-auth.cjs")

View File

@@ -24,7 +24,7 @@
"files": [
"dist/**",
"secure-password.*",
"next-auth.*"
"adapters/**"
],
"dependencies": {
"@types/b64-lite": "1.3.0",
@@ -50,9 +50,9 @@
"url": "0.11.0"
},
"peerDependencies": {
"@auth/core": "*",
"blitz": "2.0.0-beta.31",
"next": "*",
"next-auth": "*",
"secure-password": "4.0.0"
},
"peerDependenciesMeta": {
@@ -62,11 +62,12 @@
"secure-password": {
"optional": true
},
"next-auth": {
"@auth/core": {
"optional": true
}
},
"devDependencies": {
"@auth/core": "0.10.0",
"@blitzjs/config": "2.0.0-beta.31",
"@testing-library/react": "13.4.0",
"@testing-library/react-hooks": "8.0.1",
@@ -77,7 +78,6 @@
"@types/react-dom": "17.0.14",
"blitz": "2.0.0-beta.31",
"next": "13.3.0",
"next-auth": "4.18.7",
"react": "18.2.0",
"react-dom": "18.2.0",
"secure-password": "4.0.0",

1
packages/blitz-auth/src/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
!adapters

View File

@@ -0,0 +1,2 @@
export * from "./authjs/adapter"
export * from "./authjs/types"

View File

@@ -11,25 +11,26 @@ import {
secureProxyMiddleware,
truncateString,
} from "blitz"
import {isLocalhost, SessionContext} from "../../../index-server"
// next-auth internals
import {toInternalRequest, toResponse} from "./internals/utils/web"
import {getBody, getURL, setHeaders} from "./internals/utils/node"
import type {RequestInternal, AuthOptions, User} from "next-auth"
import type {Cookie} from "next-auth/core/lib/cookie"
import type {AuthAction, InternalOptions} from "./internals/core/types"
import type {
ApiHandlerIncomingMessage,
BlitzNextAuthApiHandler,
BlitzNextAuthOptions,
} from "./types"
import {Provider} from "next-auth/providers"
import {isLocalhost} from "../utils"
import {init} from "next-auth/core/init"
import getAuthorizationUrl from "next-auth/core/lib/oauth/authorization-url"
import oAuthCallback from "next-auth/core/lib/oauth/callback"
// next-auth internals
import {getBody, getURL, setHeaders} from "./internals/core/node"
import type {AuthAction, InternalOptions, RequestInternal} from "./internals/core/types"
import type {OAuthConfig} from "@auth/core/providers"
import type {Cookie} from "@auth/core/lib/cookie"
import {init} from "@auth/core/lib/init"
import emailSignIn from "@auth/core/lib/email/signin"
import {getAuthorizationUrl} from "@auth/core/lib/oauth/authorization-url"
import {handleOAuth} from "@auth/core/lib/oauth/callback"
import {handleState} from "@auth/core/lib/oauth/handle-state"
import {toInternalRequest, toResponse} from "@auth/core/lib/web"
import {assertConfig} from "@auth/core/lib/assert"
const INTERNAL_REDIRECT_URL_KEY = "_redirectUrl"
@@ -42,41 +43,38 @@ function switchURL(callbackUrl: string) {
return `${url.protocol}//${url.host}${switchPathNameString}${url.search}${url.hash}`
}
export function NextAuthAdapter<P extends Provider[]>(
export function AuthAdapter<P extends OAuthConfig<any>[]>(
config: BlitzNextAuthOptions<P>,
): BlitzNextAuthApiHandler {
return async function authHandler(req, res) {
assert(
req.query.nextauth,
"req.query.nextauth is not defined. Page must be named [...auth].ts/js.",
"req.query.nextauth is not defined. Page must be named [...nextauth].ts/js.",
)
assert(
Array.isArray(req.query.nextauth),
"req.query.nextauth must be an array. Page must be named [...auth].ts/js.",
"req.query.nextauth must be an array. Page must be named [...nextauth].ts/js.",
)
if (!req.query.nextauth?.length) {
return res.status(404).end()
}
const action = req.query.nextauth[1] as AuthAction
if (!action || !["login", "callback"].includes(action)) {
if (!action || !["signin", "callback"].includes(action)) {
return res.status(404).end()
}
const cookieSessionMiddleware = cookieSession({
secret: process.env.SESSION_SECRET_KEY || "default-dev-secret",
secure: process.env.NODE_ENV === "production" && !isLocalhost(req),
})
const middleware: RequestMiddleware<ApiHandlerIncomingMessage, MiddlewareResponse<Ctx>>[] = [
connectMiddleware(cookieSessionMiddleware as RequestMiddleware),
]
if (config.secureProxy) {
middleware.push(secureProxyMiddleware)
}
const headers = new Headers(req.headers as any)
const url = getURL(req.url, headers)
log.debug("NEXT_AUTH_URL", url)
if (url instanceof Error) {
if (process.env.NODE_ENV !== "production") throw url
const errorLogger = config.logger?.error ?? console.error
@@ -92,37 +90,46 @@ export function NextAuthAdapter<P extends Provider[]>(
method: req.method,
...getBody(req),
})
log.debug("NEXT_AUTH_REQUEST")
const internalRequest = await toInternalRequest(request)
log.debug("NEXT_AUTH_INTERNAL_REQUEST", internalRequest)
if (internalRequest instanceof Error) {
console.error((request as any).code, request)
return new Response(`Error: This action with HTTP ${request.method} is not supported.`, {
status: 400,
return res.status(500).json({
message:
"There was a problem with the server configuration. Check the server logs for more information.",
})
}
let {providerId} = internalRequest
if (providerId?.includes("?")) {
providerId = providerId.split("?")[0]
const assertionResult = assertConfig(internalRequest, config)
if (Array.isArray(assertionResult)) {
assertionResult.forEach(log.error)
} else if (assertionResult instanceof Error) {
// Bail out early if there's an error in the user config
log.error(assertionResult.message)
return res.status(500).json({
message:
"There was a problem with the server configuration. Check the server logs for more information.",
code: assertionResult.name,
})
}
const callbackUrl = req.body?.callbackUrl ?? req.query?.callbackUrl?.toString()
const {options, cookies} = await init({
// @ts-ignore
url: new URL(
// @ts-ignore
internalRequest.url!,
internalRequest.url,
process.env.APP_ORIGIN || process.env.BLITZ_DEV_SERVER_ORIGIN,
),
authOptions: config as unknown as AuthOptions,
authOptions: config,
action,
providerId,
callbackUrl: req.body?.callbackUrl ?? (req.query?.callbackUrl as string),
providerId: internalRequest.providerId.includes("?")
? internalRequest.providerId.split("?")[0]
: internalRequest.providerId,
callbackUrl,
cookies: internalRequest.cookies,
isPost: req.method === "POST",
csrfDisabled: config.csrf?.enabled ?? false,
})
options.provider.callbackUrl = switchURL(options.provider.callbackUrl)
log.debug("NEXT_AUTH_INTERNAL_OPTIONS", options)
await AuthHandler(middleware, config, internalRequest, action, options, cookies)
.then(async ({middleware}) => {
await handleRequestWithMiddleware<ApiHandlerIncomingMessage, MiddlewareResponse<Ctx>>(
@@ -145,7 +152,14 @@ export function NextAuthAdapter<P extends Provider[]>(
}
}
async function AuthHandler<P extends Provider[]>(
function defaultNormalizer(email?: string) {
if (!email) throw new Error("Missing email from request body.")
let [local, domain] = email.toLowerCase().trim().split("@")
domain = domain?.split(",")[0]
return `${local}@${domain}`
}
export async function AuthHandler<P extends OAuthConfig<any>[]>(
middleware: RequestMiddleware<ApiHandlerIncomingMessage, MiddlewareResponse<Ctx>>[],
config: BlitzNextAuthOptions<P>,
internalRequest: RequestInternal,
@@ -156,23 +170,34 @@ async function AuthHandler<P extends Provider[]>(
if (!options.provider) {
throw new OAuthError("MISSING_PROVIDER_ERROR")
}
if (action === "login") {
middleware.push(async (req, res, next) => {
if (action === "signin") {
middleware.push(async (req, res, _next) => {
try {
const _signin = await getAuthorizationUrl({options: options, query: req.query})
if (_signin.cookies) cookies.push(..._signin.cookies)
const session = res.blitzCtx.session as SessionContext
assert(session, "Missing Blitz sessionMiddleware!")
await session.$setPublicData({
[INTERNAL_REDIRECT_URL_KEY]: _signin.redirect,
} as any)
const response = toResponse(_signin)
setHeaders(response.headers, res)
res.setHeader("Location", _signin.redirect)
res.statusCode = 302
res.end()
if (options.provider.type === "oauth" || options.provider.type === "oidc") {
const _signin = await getAuthorizationUrl(req.query, options)
log.debug("NEXT_AUTH_SIGNIN", _signin)
if (_signin.cookies) cookies.push(..._signin.cookies)
await res.blitzCtx.session.$setPublicData({
[INTERNAL_REDIRECT_URL_KEY]: _signin.redirect,
} as any)
const response = toResponse(_signin)
setHeaders(response.headers, res)
log.debug("NEXT_AUTH_SIGNIN_REDIRECT", _signin.redirect)
res.setHeader("Location", _signin.redirect)
res.statusCode = 302
res.end()
} else if (options.provider.type === "email") {
const normalizer = options.provider.normalizeIdentifier ?? defaultNormalizer
const email = normalizer(internalRequest.body?.email)
const redirect = await emailSignIn(email, options, internalRequest)
res.setHeader("Location", redirect)
res.statusCode = 302
res.end()
} else {
throw new OAuthError("UNSUPPORTED_PROVIDER_ERROR")
}
} catch (e) {
log.error("OAUTH_SIGNIN_Error in NextAuthAdapter " + (e as Error).toString())
log.error("OAUTH_SIGNIN_Error in AuthAdapter " + (e as Error).toString())
console.log(e)
const authErrorQueryStringKey = config.errorRedirectUrl.includes("?")
? "&authError="
@@ -187,19 +212,27 @@ async function AuthHandler<P extends Provider[]>(
return {middleware}
} else if (action === "callback") {
middleware.push(
// eslint-disable-next-line no-shadow
connectMiddleware(async (req, res, next) => {
connectMiddleware(async (req, res, _next) => {
try {
const {profile, account, OAuthProfile} = await oAuthCallback({
query: internalRequest.query,
body: internalRequest.body || {code: req.query.code, state: req.query.state},
method: "POST",
options: options as any,
cookies: internalRequest.cookies,
})
const session = res.blitzCtx.session as SessionContext
assert(session, "Missing Blitz sessionMiddleware!")
const callback = await config.callback(profile as User, account, OAuthProfile!, session)
const {proxyRedirect, randomState} = handleState(
req.query,
options.provider,
options.isOnRedirectProxy,
)
if (proxyRedirect) {
log.debug("proxy redirect", {proxyRedirect, randomState})
res.setHeader("Location", proxyRedirect)
res.statusCode = 302
res.end()
}
const {cookies, profile, account, user} = await handleOAuth(
req.query,
internalRequest.cookies,
options,
)
log.debug("NEXT_AUTH_CALLBACK", {cookies, profile, account, user})
const session = res.blitzCtx.session
const callback = await config.callback(user, account, profile as any, session)
let _redirect = config.successRedirectUrl
if (callback instanceof Object) {
_redirect = callback.redirectUrl
@@ -208,13 +241,12 @@ async function AuthHandler<P extends Provider[]>(
redirect: _redirect,
cookies: cookies,
})
setHeaders(response.headers, res)
res.setHeader("Location", _redirect)
res.statusCode = 302
res.end()
} catch (e) {
log.error("OAUTH_CALLBACK_Error in NextAuthAdapter " + (e as Error).toString())
log.error("OAUTH_CALLBACK_Error in AuthAdapter " + (e as Error).toString())
console.log(e)
const authErrorQueryStringKey = config.errorRedirectUrl.includes("?")
? "&authError="

View File

@@ -0,0 +1,3 @@
# Auth Core Internals for Blitz
This directory contains the internals of the Auth Core being used by the blitz adapter.

View File

@@ -1,6 +1,7 @@
import type {CallbacksOptions, CookiesOptions, EventCallbacks} from "next-auth"
import type {Adapter} from "next-auth/adapters"
import type {JWTOptions} from "next-auth/jwt"
import type {AuthConfig} from "@auth/core"
import {EventCallbacks, PagesOptions, CookiesOptions, CallbacksOptions} from "@auth/core/types"
import type {Adapter} from "@auth/core/adapters"
import type {JWTOptions} from "@auth/core/jwt"
import type {
OAuthConfig,
ProviderType,
@@ -9,7 +10,7 @@ import type {
AuthorizationEndpointHandler,
EmailConfig,
CredentialsConfig,
} from "next-auth/providers"
} from "@auth/core/providers"
export interface OAuthConfigInternal<P>
extends Omit<OAuthConfig<P>, "authorization" | "token" | "userinfo"> {
@@ -33,7 +34,7 @@ export type AuthAction =
| "providers"
| "session"
| "csrf"
| "login"
| "signin"
| "signout"
| "callback"
| "verify-request"
@@ -61,26 +62,40 @@ export interface LoggerInstance extends Record<string, Function> {
debug: (code: string, metadata: unknown) => void
}
export interface InternalOptions<
TProviderType = ProviderType,
WithVerificationToken = TProviderType extends "email" ? true : false,
> {
export interface RequestInternal {
url: URL
method: "GET" | "POST"
cookies?: Partial<Record<string, string>>
headers?: Record<string, any>
query?: Record<string, any>
body?: Record<string, any>
action: AuthAction
providerId?: string
error?: string
}
export interface InternalOptions<TProviderType = ProviderType> {
providers: InternalProvider[]
url: URL
action: AuthAction
provider: InternalProvider<TProviderType>
csrfToken?: string
csrfTokenVerified?: boolean
secret: string
theme: Theme
debug: boolean
logger: LoggerInstance
session: Required<LoggerInstance>
pages: any
session: NonNullable<Required<AuthConfig["session"]>>
pages: Partial<PagesOptions>
jwt: JWTOptions
events: Partial<EventCallbacks>
adapter: WithVerificationToken extends true
? Adapter<WithVerificationToken>
: Adapter<WithVerificationToken> | undefined
adapter: Required<Adapter> | undefined
callbacks: CallbacksOptions
cookies: CookiesOptions
callbackUrl: string
/**
* If true, the OAuth callback is being proxied by the server to the original URL.
* See also {@link OAuthConfigInternal.redirectProxyUrl}.
*/
isOnRedirectProxy: boolean
}

View File

@@ -1,19 +1,22 @@
import type {Ctx, MiddlewareResponse} from "blitz"
import type {IncomingMessage} from "http"
import type {AuthOptions, Profile, User} from "next-auth"
import {SessionContext} from "../../../index-server"
import oAuthCallback from "next-auth/core/lib/oauth/callback"
import {OAuthConfig, Provider} from "next-auth/providers"
import type {Account, Profile, User} from "@auth/core/types"
import type {AuthConfig} from "@auth/core"
import type {SessionContext} from "../../index-server"
import type {OAuthConfig} from "@auth/core/providers"
export type BlitzNextAuthOptions<P extends Provider[]> = Omit<AuthOptions, "providers"> & {
export type BlitzNextAuthOptions<P extends OAuthConfig<any>[]> = Omit<AuthConfig, "providers"> & {
providers: P
successRedirectUrl: string
errorRedirectUrl: string
secureProxy?: boolean
csrf?: {
enabled: boolean
}
callback: (
user: User,
account: Awaited<ReturnType<typeof oAuthCallback>>["account"],
profile: P[0] extends OAuthConfig<any> ? Parameters<P[0]["profile"]>[0] : Profile,
user: User | undefined,
account: Account | undefined,
profile: P[number] extends OAuthConfig<infer T> ? T : Profile,
session: SessionContext,
) => Promise<void | {redirectUrl: string}>
}

View File

@@ -0,0 +1 @@
export * from "./passport/adapter"

View File

@@ -1,7 +1,7 @@
/* @eslint-disable no-redeclare */
import cookieSession from "cookie-session"
import passport from "passport"
import {isLocalhost} from "../../index"
import {isLocalhost} from "../utils"
import {
assert,
connectMiddleware,
@@ -12,7 +12,7 @@ import {
secureProxyMiddleware,
truncateString,
} from "blitz"
import {SessionContext} from "../../../shared"
import type {SessionContext} from "../../shared"
import {
BlitzPassportConfig,
ApiHandler,

View File

@@ -1,6 +1,6 @@
import type {AuthenticateOptions, Strategy} from "passport"
import type {IncomingMessage, ServerResponse} from "http"
import type {PublicData} from "../../../shared"
import type {PublicData} from "../../shared"
import type {Ctx, MiddlewareResponse} from "blitz"
export interface BlitzPassportConfigCallbackParams {

View File

@@ -0,0 +1,9 @@
export function isLocalhost(req: any): boolean {
let {host} = req.headers
let localhost = false
if (host) {
host = host.split(":")[0]
localhost = host === "localhost"
}
return localhost
}

View File

@@ -1,4 +1,4 @@
import "./global"
export * from "./adapters"
export * from "./index-browser"
export * from "./server"

View File

@@ -1,2 +0,0 @@
export * from "./passport/adapter"
export * from "./next-auth/webpack"

View File

@@ -1,2 +0,0 @@
export * from "./next-auth/adapter"
export * from "./next-auth/types"

View File

@@ -1,3 +0,0 @@
# Next-auth Internals
This directory contains the internals of the Next-auth being used by the blitz adapter.

View File

@@ -1,39 +0,0 @@
export interface InternalUrl {
/** @default "http://localhost:3000" */
origin: string
/** @default "localhost:3000" */
host: string
/** @default "/api/auth" */
path: string
/** @default "http://localhost:3000/api/auth" */
base: string
/** @default "http://localhost:3000/api/auth" */
toString: () => string
}
/**
* TODO: Can we remove this?
* Returns an `URL` like object to make requests/redirects from server-side
*/
export default function parseUrl(url?: string | URL): InternalUrl {
const defaultUrl = new URL("http://localhost:3000/api/auth")
if (url && !url.toString().startsWith("http")) {
url = `https://${url}`
}
const _url = new URL(url ?? defaultUrl)
const path = (_url.pathname === "/" ? defaultUrl.pathname : _url.pathname)
// Remove trailing slash
.replace(/\/$/, "")
const base = `${_url.origin}${path}`
return {
origin: _url.origin,
host: _url.host,
path,
base,
toString: () => base,
}
}

View File

@@ -1,115 +0,0 @@
import {OAuthError} from "blitz"
import {serialize, parse as parseCookie} from "cookie"
import type {ResponseInternal, RequestInternal} from "next-auth/core"
import type {AuthAction} from "next-auth/core/types"
const decoder = new TextDecoder()
async function streamToString(stream: any): Promise<string> {
const chunks: Uint8Array[] = []
return await new Promise((resolve, reject) => {
stream.on("data", (chunk: WithImplicitCoercion<ArrayBuffer | SharedArrayBuffer>) =>
chunks.push(Buffer.from(chunk)),
)
stream.on("error", (err: any) => reject(err))
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
})
}
async function readJSONBody(
body: ReadableStream | Buffer,
): Promise<Record<string, any> | undefined> {
try {
if ("getReader" in body) {
const reader = body.getReader()
const bytes: number[] = []
while (true) {
const {value, done} = await reader.read()
if (done) break
bytes.push(...value)
}
const b = new Uint8Array(bytes)
return JSON.parse(decoder.decode(b))
}
// node-fetch
if (typeof Buffer !== "undefined" && Buffer.isBuffer(body)) {
return JSON.parse(body.toString("utf8"))
}
return JSON.parse(await streamToString(body))
} catch (e) {
console.error(e)
}
}
// prettier-ignore
const actions = [ "providers", "session", "csrf", "login", "signout", "callback", "verify-request", "error", "_log"]
export async function toInternalRequest(req: Request): Promise<RequestInternal | Error> {
try {
// TODO: url.toString() should not include action and providerId
// see init.ts
const url = new URL(req.url.replace(/\/$/, ""))
const {pathname} = url
const action = actions.find((a) => pathname.includes(a)) as AuthAction | undefined
if (!action) {
throw new OAuthError("Cannot detect action.")
}
const providerIdOrAction = pathname.split("/").pop()
let providerId
if (
providerIdOrAction &&
!action.includes(providerIdOrAction) &&
["login", "callback"].includes(action)
) {
providerId = providerIdOrAction
}
return {
//@ts-ignore
url,
action,
providerId,
method: req.method ?? "GET",
headers: Object.fromEntries(req.headers),
body: req.body ? await readJSONBody(req.body) : undefined,
cookies: parseCookie(req.headers.get("cookie") ?? "") ?? {},
error: url.searchParams.get("error") ?? undefined,
query: Object.fromEntries(url.searchParams),
}
} catch (error) {
return error as Error
}
}
export function toResponse(res: ResponseInternal): Response {
const headers = new Headers(res.headers as unknown as HeadersInit)
res.cookies?.forEach((cookie) => {
const {name, value, options} = cookie
const cookieHeader = serialize(name, value, options)
if (headers.has("Set-Cookie")) {
headers.append("Set-Cookie", cookieHeader)
} else {
headers.set("Set-Cookie", cookieHeader)
}
})
const body =
headers.get("content-type") === "application/json" ? JSON.stringify(res.body) : res.body
const response = new Response(body, {
headers,
status: res.redirect ? 302 : res.status ?? 200,
})
if (res.redirect) {
response.headers.set("Location", res.redirect)
}
return response
}

View File

@@ -1,30 +0,0 @@
//@ts-nocheck
import path from "path"
export function withNextAuthAdapter(nextConfig) {
const config = Object.assign({}, nextConfig)
try {
const nextAuthPath = path.dirname(require.resolve("next-auth"))
const webpack = (config) => {
config.resolve.alias = {
...config.resolve.alias,
"next-auth/core/lib/oauth/callback": path.join(nextAuthPath, "core/lib/oauth/callback.js"),
"next-auth/core/lib/oauth/authorization-url": path.join(
nextAuthPath,
"core/lib/oauth/authorization-url.js",
),
"next-auth/core/init": path.join(nextAuthPath, "core/init.js"),
}
return config
}
if (typeof nextConfig.webpack === "function") {
config.webpack = (config, options) => {
return nextConfig.webpack(webpack(config), options)
}
}
config.webpack = webpack
return config
} catch (e) {
return config
}
}

View File

@@ -190,7 +190,7 @@ export async function getBlitzContext(): Promise<Ctx> {
const req = new IncomingMessage(new Socket()) as IncomingMessage & {
cookies: {[key: string]: string}
}
req.headers = Object.fromEntries(headers())
req.headers = Object.fromEntries(headers() as any)
const csrfToken = cookies().get(COOKIE_CSRF_TOKEN())
if (csrfToken) {
req.headers[HEADER_CSRF] = csrfToken.value

View File

@@ -1,7 +1,5 @@
export * from "./auth-utils"
export * from "./auth-plugin"
export * from "./adapters"
export {
SessionContextClass,
getAllSessionHandlesForUser,

View File

@@ -0,0 +1,39 @@
diff --git a/package.json b/package.json
index dbd1f7f8e24dd70d74bb1bd5ad7bb1a244919ead..72b04f35527ec98dba059ba9e03eb35c08a27c1c 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,34 @@
},
"./types": {
"types": "./types.d.ts"
+ },
+ "./lib/init": {
+ "types": "./lib/init.d.ts",
+ "import": "./lib/init.js"
+ },
+ "./lib/email/signin":{
+ "types": "./lib/email/signin.d.ts",
+ "import": "./lib/email/signin.js"
+ },
+ "./lib/oauth/authorization-url": {
+ "types": "./lib/oauth/authorization-url.d.ts",
+ "import": "./lib/oauth/authorization-url.js"
+ },
+ "./lib/oauth/callback": {
+ "types": "./lib/oauth/callback.d.ts",
+ "import": "./lib/oauth/callback.js"
+ },
+ "./lib/oauth/handle-state": {
+ "types": "./lib/oauth/handle-state.d.ts",
+ "import": "./lib/oauth/handle-state.js"
+ },
+ "./lib/assert": {
+ "types": "./lib/assert.d.ts",
+ "import": "./lib/assert.js"
+ },
+ "./lib/web": {
+ "types": "./lib/web.d.ts",
+ "import": "./lib/web.js"
}
},
"license": "ISC",

82
pnpm-lock.yaml generated
View File

@@ -4,6 +4,11 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
"@auth/core@0.10.0":
hash: jkkk4das3mr53z5xj5wygbcfda
path: patches/@auth__core@0.10.0.patch
importers:
.:
dependencies:
@@ -125,6 +130,9 @@ importers:
apps/toolkit-app:
dependencies:
"@auth/core":
specifier: 0.10.0
version: 0.10.0(patch_hash=jkkk4das3mr53z5xj5wygbcfda)
"@blitzjs/auth":
specifier: 2.0.0-beta.31
version: link:../../packages/blitz-auth
@@ -152,9 +160,6 @@ importers:
next:
specifier: 13.3.0
version: 13.3.0(@babel/core@7.20.2)(react-dom@18.2.0)(react@18.2.0)
next-auth:
specifier: 4.18.7
version: 4.18.7(next@13.3.0)(react-dom@18.2.0)(react@18.2.0)
prisma:
specifier: 4.6.1
version: 4.6.1
@@ -1578,6 +1583,9 @@ importers:
specifier: 0.11.0
version: 0.11.0
devDependencies:
"@auth/core":
specifier: 0.10.0
version: 0.10.0(patch_hash=jkkk4das3mr53z5xj5wygbcfda)
"@blitzjs/config":
specifier: 2.0.0-beta.31
version: link:../config
@@ -1608,9 +1616,6 @@ importers:
next:
specifier: 13.3.0
version: 13.3.0(@babel/core@7.20.2)(react-dom@18.2.0)(react@18.2.0)
next-auth:
specifier: 4.18.7
version: 4.18.7(next@13.3.0)(react-dom@18.2.0)(react@18.2.0)
react:
specifier: 18.2.0
version: 18.2.0
@@ -2358,6 +2363,25 @@ packages:
"@jridgewell/gen-mapping": 0.1.1
"@jridgewell/trace-mapping": 0.3.17
/@auth/core@0.10.0(patch_hash=jkkk4das3mr53z5xj5wygbcfda):
resolution:
{
integrity: sha512-mmvAzFUcDHG0m6avQ6/sYI3wtQtt3Tjbk4wCr27OlCeNnlzqK8lDQJIDFbJOTXEu/dNUx8sUQ0xMp7A6GAaHQw==,
}
peerDependencies:
nodemailer: ^6.8.0
peerDependenciesMeta:
nodemailer:
optional: true
dependencies:
"@panva/hkdf": 1.1.1
cookie: 0.5.0
jose: 4.11.2
oauth4webapi: 2.3.0
preact: 10.11.3
preact-render-to-string: 5.2.3(preact@10.11.3)
patched: true
/@babel/code-frame@7.16.7:
resolution:
{
@@ -5717,10 +5741,10 @@ packages:
"@nodelib/fs.scandir": 2.1.5
fastq: 1.13.0
/@panva/hkdf@1.0.2:
/@panva/hkdf@1.1.1:
resolution:
{
integrity: sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==,
integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==,
}
/@pkgr/utils@2.3.1:
@@ -16715,34 +16739,6 @@ packages:
}
dev: false
/next-auth@4.18.7(next@13.3.0)(react-dom@18.2.0)(react@18.2.0):
resolution:
{
integrity: sha512-kR3s1JVPMaDuSAlFxcGyv7Ec3fdE6za71r1F77IOII5zJmW2wfkIA2xj223fM0D20ip2pzFpHfk/qN4L6l5XMA==,
}
engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0}
peerDependencies:
next: ^12.2.5 || ^13
nodemailer: ^6.6.5
react: ^17.0.2 || ^18
react-dom: ^17.0.2 || ^18
peerDependenciesMeta:
nodemailer:
optional: true
dependencies:
"@babel/runtime": 7.18.3
"@panva/hkdf": 1.0.2
cookie: 0.5.0
jose: 4.11.2
next: 13.3.0(@babel/core@7.20.2)(react-dom@18.2.0)(react@18.2.0)
oauth: 0.9.15
openid-client: 5.2.1
preact: 10.11.3
preact-render-to-string: 5.2.6(preact@10.11.3)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
uuid: 8.3.2
/next-router-mock@0.9.1(next@13.3.0)(react@18.2.0):
resolution:
{
@@ -17068,6 +17064,12 @@ packages:
integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==,
}
/oauth4webapi@2.3.0:
resolution:
{
integrity: sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==,
}
/oauth@0.10.0:
resolution:
{
@@ -17080,6 +17082,7 @@ packages:
{
integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==,
}
dev: false
/object-assign@4.1.1:
resolution:
@@ -17106,6 +17109,7 @@ packages:
integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==,
}
engines: {node: ">= 6"}
dev: false
/object-inspect@1.12.2:
resolution:
@@ -17200,6 +17204,7 @@ packages:
integrity: sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==,
}
engines: {node: ^10.13.0 || >=12.0.0}
dev: false
/on-finished@2.3.0:
resolution:
@@ -17297,6 +17302,7 @@ packages:
lru-cache: 6.0.0
object-hash: 2.2.0
oidc-token-hash: 5.0.1
dev: false
/optionator@0.8.3:
resolution:
@@ -17990,10 +17996,10 @@ packages:
picocolors: 1.0.0
source-map-js: 1.0.2
/preact-render-to-string@5.2.6(preact@10.11.3):
/preact-render-to-string@5.2.3(preact@10.11.3):
resolution:
{
integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==,
integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==,
}
peerDependencies:
preact: ">=10"