Skip to content

Commit

Permalink
refactor: strict types (#2802)
Browse files Browse the repository at this point in the history
* WIP strict types

* wip types

* wip strict types

* More strict typing

* Removing strict false
Fix last types

* Fix typo

* Make TS happy

* Fix tests

* Fixes to types

* Make files align with strict mode
  • Loading branch information
Thisen committed Nov 4, 2021
1 parent 533ed94 commit f998bf2
Show file tree
Hide file tree
Showing 29 changed files with 187 additions and 127 deletions.
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 type { 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

0 comments on commit f998bf2

Please sign in to comment.