Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: strict types #2802

Merged
merged 15 commits into from
Nov 4, 2021
8 changes: 4 additions & 4 deletions src/client/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function fetchData<T = any>(
return Object.keys(data).length > 0 ? data : null // Return null if data empty
} catch (error) {
logger.error("CLIENT_FETCH_ERROR", {
error,
error: error as Error,
path,
...(req ? { header: req.headers } : {}),
})
Expand Down Expand Up @@ -84,9 +84,9 @@ export function BroadcastChannel(name = "nextauth.message") {
return {
/** Get notified by other tabs/windows. */
receive(onReceive: (message: BroadcastMessage) => void) {
const handler = (event) => {
const handler = (event: StorageEvent) => {
if (event.key !== name) return
const message: BroadcastMessage = JSON.parse(event.newValue)
const message: BroadcastMessage = JSON.parse(event.newValue ?? "{}")
if (message?.event !== "session" || !message?.data) return

onReceive(message)
Expand All @@ -95,7 +95,7 @@ export function BroadcastChannel(name = "nextauth.message") {
return () => window.removeEventListener("storage", handler)
},
/** Notify other tabs/windows. */
post(message) {
post(message: Record<string, unknown>) {
if (typeof window === "undefined") return
localStorage.setItem(
name,
Expand Down
14 changes: 7 additions & 7 deletions src/core/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { Adapter } from "../adapters"
* @source https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
*/
export class UnknownError extends Error {
constructor(error) {
constructor(error: Error | string) {
// Support passing error or string
super(error?.message ?? error)
super((error as Error)?.message ?? error)
this.name = "UnknownError"
if (error instanceof Error) {
this.stack = error.stack
Expand Down Expand Up @@ -56,10 +56,10 @@ export function eventsErrorHandler(
return Object.keys(methods).reduce<any>((acc, name) => {
acc[name] = async (...args: any[]) => {
try {
const method: Method = methods[name]
const method: Method = methods[name as keyof Method]
return await method(...args)
} catch (e) {
logger.error(`${upperSnake(name)}_EVENT_ERROR`, e)
logger.error(`${upperSnake(name)}_EVENT_ERROR`, e as Error)
}
}
return acc
Expand All @@ -77,11 +77,11 @@ export function adapterErrorHandler(
acc[name] = async (...args: any[]) => {
try {
logger.debug(`adapter_${name}`, { args })
const method: Method = adapter[name as any]
const method: Method = adapter[name as keyof Method]
return await method(...args)
} catch (error) {
logger.error(`adapter_error_${name}`, error)
const e = new UnknownError(error)
logger.error(`adapter_error_${name}`, error as Error)
const e = new UnknownError(error as Error)
e.name = `${capitalize(name)}Error`
throw e
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export async function NextAuthHandler<
logger[level](code, metadata)
} catch (error) {
// If logging itself failed...
logger.error("LOGGER_ERROR", error)
logger.error("LOGGER_ERROR", error as Error)
}
}
return {}
Expand Down
41 changes: 24 additions & 17 deletions src/core/lib/cookie.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
// REVIEW: Is there any way to defer two types of strings?

import { NextAuthResponse } from "../../lib/types"
import { CookiesOptions } from "../.."
import { CookieOption } from "../types"
import { ServerResponse } from "http"

/** Stringified form of `JWT`. Extract the content with `jwt.decode` */
export type JWTString = string

export type SetCookieOptions = Partial<CookieOption["options"]> & {
expires?: Date | string
encode?: (val: unknown) => string
}

/** If `options.session.jwt` is set to `true`, this is a stringified `JWT`. In case of a database persisted session, this is the `sessionToken` of the session in the database.. */
export type SessionToken<T extends "jwt" | "db" = "jwt"> = T extends "jwt"
? JWTString
Expand All @@ -22,13 +28,10 @@ export type SessionToken<T extends "jwt" | "db" = "jwt"> = T extends "jwt"
* (with fixes for specific issues) to keep dependancy size down.
*/
export function set(
res,
name,
value,
options: {
expires?: Date
maxAge?: number
} = {}
res: NextAuthResponse | ServerResponse,
name: string,
value: unknown,
options: SetCookieOptions = {}
) {
const stringValue =
typeof value === "object" ? "j:" + JSON.stringify(value) : String(value)
Expand All @@ -39,20 +42,24 @@ export function set(
}

// Preserve any existing cookies that have already been set in the same session
let setCookieHeader = res.getHeader("Set-Cookie") || []
let setCookieHeader = res.getHeader("Set-Cookie") ?? []
// If not an array (i.e. a string with a single cookie) convert it into an array
if (!Array.isArray(setCookieHeader)) {
setCookieHeader = [setCookieHeader]
setCookieHeader = [setCookieHeader.toString()]
}
setCookieHeader.push(_serialize(name, String(stringValue), options))
res.setHeader("Set-Cookie", setCookieHeader)
}

function _serialize(name, val, options) {
function _serialize(
name: string,
val: unknown,
options: SetCookieOptions = {}
) {
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/ // eslint-disable-line no-control-regex

const opt = options || {}
const enc = opt.encode || encodeURIComponent
const enc = opt.encode ?? encodeURIComponent

if (typeof enc !== "function") {
throw new TypeError("option encode is invalid")
Expand All @@ -62,7 +69,7 @@ function _serialize(name, val, options) {
throw new TypeError("argument name is invalid")
}

const value = enc(val)
const value = enc(val as string)

if (value && !fieldContentRegExp.test(value)) {
throw new TypeError("argument val is invalid")
Expand Down Expand Up @@ -99,9 +106,9 @@ function _serialize(name, val, options) {
}

if (opt.expires) {
let expires = opt.expires
if (typeof opt.expires.toUTCString === "function") {
expires = opt.expires.toUTCString()
let expires: Date | string = opt.expires
if (typeof (opt.expires as Date).toUTCString === "function") {
expires = (opt.expires as Date).toUTCString()
} else {
const dateExpires = new Date(opt.expires)
expires = dateExpires.toUTCString()
Expand Down Expand Up @@ -154,7 +161,7 @@ function _serialize(name, val, options) {
*
* @TODO Review cookie settings (names, options)
*/
export function defaultCookies(useSecureCookies): CookiesOptions {
export function defaultCookies(useSecureCookies: boolean): CookiesOptions {
const cookiePrefix = useSecureCookies ? "__Secure-" : ""
return {
// default cookie options
Expand Down
2 changes: 1 addition & 1 deletion src/core/lib/email/signin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default async function email(
logger.error("SEND_VERIFICATION_EMAIL_ERROR", {
identifier,
url,
error,
error: error as Error,
})
throw new Error("SEND_VERIFICATION_EMAIL_ERROR")
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/lib/oauth/authorization-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default async function getAuthorizationUrl(params: {
cookies,
}
} catch (error) {
logger.error("GET_AUTHORIZATION_URL_ERROR", error)
logger.error("GET_AUTHORIZATION_URL_ERROR", error as Error)
throw error
}
}
15 changes: 9 additions & 6 deletions src/core/lib/oauth/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ export default async function oAuthCallback(params: {
const { oauth_token, oauth_verifier } = query ?? {}
// @ts-expect-error
const tokens: TokenSet = await client.getOAuthAccessToken(
oauth_token,
oauth_token as string,
// @ts-expect-error
null,
oauth_verifier
)
// @ts-expect-error
let profile: Profile = await client.get(
provider.profileUrl,
(provider as any).profileUrl,
tokens.oauth_token,
tokens.oauth_token_secret
)
Expand All @@ -56,7 +56,7 @@ export default async function oAuthCallback(params: {

return await getProfile({ profile, tokens, provider, logger })
} catch (error) {
logger.error("OAUTH_V1_GET_ACCESS_TOKEN_ERROR", error)
logger.error("OAUTH_V1_GET_ACCESS_TOKEN_ERROR", error as Error)
throw error
}
}
Expand Down Expand Up @@ -141,8 +141,8 @@ export default async function oAuthCallback(params: {
cookies: pkce?.cookie ? [pkce.cookie] : undefined,
}
} catch (error) {
logger.error("OAUTH_CALLBACK_ERROR", { error, providerId: provider.id })
throw new OAuthCallbackError(error)
logger.error("OAUTH_CALLBACK_ERROR", { error: error as Error, providerId: provider.id })
throw new OAuthCallbackError(error as Error)
}
}

Expand Down Expand Up @@ -191,7 +191,10 @@ async function getProfile({
// all providers, so we return an empty object; the user should then be
// redirected back to the sign up page. We log the error to help developers
// who might be trying to debug this when configuring a new provider.
logger.error("OAUTH_PARSE_PROFILE_ERROR", { error, OAuthProfile })
logger.error("OAUTH_PARSE_PROFILE_ERROR", {
error: error as Error,
OAuthProfile,
})
return {
profile: null,
account: null,
Expand Down
6 changes: 3 additions & 3 deletions src/core/lib/oauth/client-legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ export function oAuth1Client(options: InternalOptions<"oauth">) {
return await new Promise((resolve, reject) => {
originalGetOAuth1AccessToken(
...args,
(error, oauth_token, oauth_token_secret) => {
(error: any, oauth_token: any, oauth_token_secret: any) => {
if (error) {
return reject(error)
}
resolve({ oauth_token, oauth_token_secret })
resolve({ oauth_token, oauth_token_secret } as any)
}
)
})
Expand All @@ -60,7 +60,7 @@ export function oAuth1Client(options: InternalOptions<"oauth">) {
if (error) {
return reject(error)
}
resolve({ oauth_token, oauth_token_secret, params })
resolve({ oauth_token, oauth_token_secret, params } as any)
}
)
})
Expand Down
6 changes: 2 additions & 4 deletions src/core/lib/oauth/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,8 @@ export async function openidClient(

const client = new issuer.Client(
{
// @ts-expect-error
client_id: provider.clientId,
// @ts-expect-error
client_secret: provider.clientSecret,
client_id: provider.clientId as string,
client_secret: provider.clientSecret as string,
redirect_uris: [provider.callbackUrl],
...provider.client,
},
Expand Down
19 changes: 15 additions & 4 deletions src/core/lib/oauth/pkce-handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as cookie from "../cookie"
import { Cookie } from "../cookie"
import * as jwt from "../../../jwt"
import { generators } from "openid-client"
import { InternalOptions } from "src/lib/types"
Expand All @@ -16,7 +16,17 @@ const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
* cookie: import("../cookie").Cookie
* }>
*/
export async function createPKCE(options) {

type PKCE = Promise<
| undefined
| {
code_challenge: string
code_challenge_method: "S256"
cookie: Cookie
}
>

export async function createPKCE(options: InternalOptions<"oauth">): PKCE {
const { cookies, logger } = options
/** @type {import("src/providers").OAuthConfig} */
const provider = options.provider
Expand All @@ -29,6 +39,7 @@ export async function createPKCE(options) {

// Encrypt code_verifier and save it to an encrypted cookie
const encryptedCodeVerifier = await jwt.encode({
// @ts-expect-error
maxAge: PKCE_MAX_AGE,
...options.jwt,
token: { code_verifier: codeVerifier },
Expand Down Expand Up @@ -68,7 +79,7 @@ export async function usePKCECodeVerifier(params: {
}): Promise<
| {
codeVerifier?: string
cookie?: cookie.Cookie
cookie?: Cookie
}
| undefined
> {
Expand All @@ -85,7 +96,7 @@ export async function usePKCECodeVerifier(params: {
})

// remove PKCE cookie after it has been used up
const cookie: cookie.Cookie = {
const cookie: Cookie = {
name: cookies.pkceCodeVerifier.name,
value: "",
options: {
Expand Down
38 changes: 19 additions & 19 deletions src/core/lib/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,33 +36,33 @@ export default function parseProviders(params: {
function normalizeProvider(provider?: Provider) {
if (!provider) return

const normalizedProvider: any = Object.entries(provider).reduce(
(acc, [key, value]) => {
if (
["authorization", "token", "userinfo"].includes(key) &&
typeof value === "string"
) {
const url = new URL(value)
acc[key] = {
url: `${url.origin}${url.pathname}`,
params: Object.fromEntries(url.searchParams ?? []),
}
} else {
acc[key] = value
const normalizedProvider: InternalProvider = Object.entries(
provider
).reduce<InternalProvider>((acc, [key, value]) => {
if (
["authorization", "token", "userinfo"].includes(key) &&
typeof value === "string"
) {
const url = new URL(value)
;(acc as any)[key] = {
url: `${url.origin}${url.pathname}`,
params: Object.fromEntries(url.searchParams ?? []),
}
} else {
acc[key as keyof InternalProvider] = value
}

return acc
},
{}
)
return acc
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
}, {} as InternalProvider)

// Checks only work on OAuth 2.x + OIDC providers
if (
provider.type === "oauth" &&
!provider.version?.startsWith("1.") &&
!provider.checks
) {
normalizedProvider.checks = ["state"]
;(normalizedProvider as InternalProvider<"oauth">).checks = ["state"]
}
return normalizedProvider as InternalProvider
return normalizedProvider
}
2 changes: 1 addition & 1 deletion src/core/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { InternalUrl } from "../../lib/parse-url"
* Optionally takes a second date parameter. In that case
* the date in the future will be calculated from that date instead of now.
*/
export function fromDate(time, date = Date.now()) {
export function fromDate(time: number, date = Date.now()) {
return new Date(date + time * 1000)
}

Expand Down
Loading