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

fix: add custom error classes #341

Merged
merged 3 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/GoTrueApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { COOKIE_OPTIONS } from './lib/constants'
import { setCookies, getCookieString } from './lib/cookies'
import { expiresAt, resolveFetch } from './lib/helpers'

import { isAuthError, AuthError, AuthApiError } from './lib/errors'
import { isAuthError, AuthError, AuthApiError, AuthSessionMissingError, AuthEventMissingError, AuthNoCookieError } from './lib/errors'

export default class GoTrueApi {
protected url: string
Expand Down Expand Up @@ -632,9 +632,9 @@ export default class GoTrueApi {
}
const { event, session } = req.body

if (!event) throw new Error('Auth event missing!')
if (!event) throw new AuthEventMissingError()
if (event === 'SIGNED_IN') {
if (!session) throw new Error('Auth session missing!')
if (!session) throw new AuthSessionMissingError()
setCookies(
req,
res,
Expand Down Expand Up @@ -697,9 +697,9 @@ export default class GoTrueApi {
}
const { event, session } = req.body

if (!event) throw new Error('Auth event missing!')
if (!event) throw new AuthEventMissingError()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More a question than a review comment: when will we have auth events go missing? Not too sure how the events portion works.

If there's time, can I trouble you for a short overview pre/post sync?

if (event === 'SIGNED_IN') {
if (!session) throw new Error('Auth session missing!')
if (!session) throw new AuthSessionMissingError()
return getCookieString(
req,
res,
Expand Down Expand Up @@ -885,7 +885,7 @@ export default class GoTrueApi {
const refresh_token = req.cookies[`${this.cookieName()}-refresh-token`]

if (!access_token) {
throw new Error('No cookie found!')
throw new AuthNoCookieError()
}

const { user, error } = await this.getUser(access_token)
Expand Down
65 changes: 29 additions & 36 deletions src/GoTrueClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { polyfillGlobalThis } from './lib/polyfills'
import { Fetch } from './lib/fetch'

import { isAuthError, AuthError, AuthApiError } from './lib/errors'
import { isAuthError, AuthError, AuthApiError, AuthSessionMissingError, AuthInvalidCredentialsError, AuthUnknownError } from './lib/errors'

import type {
Session,
Expand All @@ -37,7 +37,7 @@ import type {
SignInWithOAuthCredentials,
SignInWithPasswordlessCredentials,
AuthResponse,
OAuthResposne,
OAuthResponse,
} from './lib/types'

polyfillGlobalThis() // Make "globalThis" available
Expand Down Expand Up @@ -86,7 +86,7 @@ export default class GoTrueClient {
* Create a new client for use in the browser.
* @param options.url The URL of the GoTrue server.
* @param options.headers Any additional headers to send to the GoTrue server.
* @param options.storageKey
* @param options.storageKey
* @param options.detectSessionInUrl Set to "true" if you want to automatically detects OAuth grants in the URL and signs in the user.
* @param options.autoRefreshToken Set to "true" if you want to automatically refresh the token before expiring.
* @param options.persistSession Set to "true" if you want to automatically save the user session into local storage. If set to false, session will just be saved in memory.
Expand Down Expand Up @@ -129,7 +129,7 @@ export default class GoTrueClient {
// Handle the OAuth redirect
this.getSessionFromUrl({ storeSession: true }).then(({ error }) => {
if (error) {
throw new Error('Error getting session from URL.')
throw new AuthUnknownError('Error getting session from URL.', error)
}
})
}
Expand Down Expand Up @@ -215,14 +215,14 @@ export default class GoTrueClient {
captchaToken: options?.captchaToken,
})
}

if ('phone' in credentials) {
let { phone, password, options } = credentials
return this._handlePhoneSignIn(phone, password, {
captchaToken: options?.captchaToken
captchaToken: options?.captchaToken,
})
}
throw new Error(`You must provide either an email or phone number and a password.`)
throw new AuthInvalidCredentialsError('You must provide either an email or phone number and a password.')
} catch (error) {
if (isAuthError(error)) {
return { user: null, session: null, error }
Expand All @@ -238,11 +238,9 @@ export default class GoTrueClient {
* @param provider One of the providers supported by GoTrue.
* @param redirectTo A URL to send the user to after they are confirmed (OAuth logins only).
* @param scopes A space-separated list of scopes granted to the OAuth application.
* @param queryParams An object of query params
* @param queryParams An object of query params
*/
async signInWithOAuth(
credentials: SignInWithOAuthCredentials,
): Promise<OAuthResposne> {
async signInWithOAuth(credentials: SignInWithOAuthCredentials): Promise<OAuthResponse> {
try {
this._removeSession()
return this._handleProviderSignIn(credentials.provider, {
Expand All @@ -262,9 +260,7 @@ export default class GoTrueClient {
* @param phone The user's phone number.
* @param options Valid options for passwordless sign-ins.
*/
async signInWithOtp(
credentials: SignInWithPasswordlessCredentials,
): Promise<AuthResponse> {
async signInWithOtp(credentials: SignInWithPasswordlessCredentials): Promise<AuthResponse> {
try {
this._removeSession()

Expand All @@ -285,9 +281,7 @@ export default class GoTrueClient {
})
return { user: null, session: null, error }
}
throw new Error(
`You must provide either an email or phone number.`
)
throw new AuthInvalidCredentialsError('You must provide either an email or phone number.')
} catch (error) {
if (isAuthError(error)) {
return { user: null, session: null, error }
Expand Down Expand Up @@ -439,14 +433,10 @@ export default class GoTrueClient {
throw sessionError
}
if (!session) {
throw new Error('Not logged in')
throw new AuthSessionMissingError()
}
const { user, error: userError } = await this.api.updateUser(
session.access_token,
attributes
)
const { user, error: userError } = await this.api.updateUser(session.access_token, attributes)
if (userError) throw userError
if (!user) throw Error('Invalid user data.')
session.user = user
this._saveSession(session)
this._notifyAllSubscribers('USER_UPDATED', session)
Expand Down Expand Up @@ -475,7 +465,7 @@ export default class GoTrueClient {
> {
try {
if (!refresh_token) {
throw new Error('No current session.')
throw new AuthSessionMissingError()
}
const { session, error } = await this.api.refreshAccessToken(refresh_token)
if (error) {
Expand Down Expand Up @@ -509,17 +499,17 @@ export default class GoTrueClient {
if (!isBrowser()) throw new AuthApiError('No browser detected.', 500)

const error_description = getParameterByName('error_description')
if (error_description) throw new Error(error_description)
if (error_description) throw new AuthApiError(error_description, 500)

const provider_token = getParameterByName('provider_token')
const access_token = getParameterByName('access_token')
if (!access_token) throw new Error('No access_token detected.')
if (!access_token) throw new AuthApiError('No access_token detected.', 500)
const expires_in = getParameterByName('expires_in')
if (!expires_in) throw new Error('No expires_in detected.')
if (!expires_in) throw new AuthApiError('No expires_in detected.', 500)
const refresh_token = getParameterByName('refresh_token')
if (!refresh_token) throw new Error('No refresh_token detected.')
if (!refresh_token) throw new AuthApiError('No refresh_token detected.', 500)
const token_type = getParameterByName('token_type')
if (!token_type) throw new Error('No token_type detected.')
if (!token_type) throw new AuthApiError('No token_type detected.', 500)

const timeNow = Math.round(Date.now() / 1000)
const expires_at = timeNow + parseInt(expires_in)
Expand Down Expand Up @@ -566,15 +556,15 @@ export default class GoTrueClient {
async signOut(): Promise<{ error: AuthError | null }> {
const { session, error: sessionError } = await this.getSession()
if (sessionError) {
throw sessionError
return { error: sessionError }
}
const accessToken = session?.access_token
this._removeSession()
this._notifyAllSubscribers('SIGNED_OUT', session)
if (accessToken) {
const { error } = await this.api.signOut(accessToken)
if (error) return { error }
}
this._removeSession()
this._notifyAllSubscribers('SIGNED_OUT', null)
return { error: null }
}

Expand Down Expand Up @@ -723,7 +713,7 @@ export default class GoTrueClient {
throw error
}
}
throw new Error(`You must provide an OpenID Connect provider with your id token and nonce.`)
throw new AuthInvalidCredentialsError('You must provide an OpenID Connect provider with your id token and nonce.')
}

/**
Expand Down Expand Up @@ -814,11 +804,11 @@ export default class GoTrueClient {
}>()

if (!refreshToken) {
throw new Error('No current session.')
throw new AuthSessionMissingError()
}
const { session, error } = await this.api.refreshAccessToken(refreshToken)
if (error) throw error
if (!session) throw Error('Invalid session session.')
if (!session) throw new AuthSessionMissingError()

this._saveSession(session)
this._notifyAllSubscribers('TOKEN_REFRESHED', session)
Expand Down Expand Up @@ -900,7 +890,10 @@ export default class GoTrueClient {
error?.message === NETWORK_FAILURE.ERROR_MESSAGE &&
this.networkRetries < NETWORK_FAILURE.MAX_RETRIES
)
this._startAutoRefreshToken(NETWORK_FAILURE.RETRY_INTERVAL ** this.networkRetries * 100, session) // exponential backoff
this._startAutoRefreshToken(
NETWORK_FAILURE.RETRY_INTERVAL ** this.networkRetries * 100,
session
) // exponential backoff
}, value)
if (typeof this.refreshTokenTimer.unref === 'function') this.refreshTokenTimer.unref()
}
Expand Down
4 changes: 3 additions & 1 deletion src/lib/cookies.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AuthUnknownError } from "./errors"

type Cookie = {
name: string
value: string
Expand Down Expand Up @@ -112,7 +114,7 @@ function serialize(
*/
function isSecureEnvironment(req: any) {
if (!req || !req.headers || !req.headers.host) {
throw new Error('The "host" request header is not available')
throw new AuthUnknownError('The "host" request header is not available', new Error('The "host" request header is not available'))
kangmingtay marked this conversation as resolved.
Show resolved Hide resolved
}

const host =
Expand Down
42 changes: 42 additions & 0 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,45 @@ export class AuthUnknownError extends AuthError {
this.originalError = originalError
}
}

export class CustomAuthError extends AuthError {
name: string
status: number
constructor(message: string, name: string, status: number) {
super(message)
this.name = name
this.status = status
}

toJSON() {
return {
name: this.name,
message: this.message,
status: this.status
}
}
}

export class AuthEventMissingError extends CustomAuthError {
constructor() {
super('Auth event missing!', 'AuthEventMissingError', 400)
}
}

export class AuthSessionMissingError extends CustomAuthError {
constructor() {
super('Auth session missing!', 'AuthSessionMissingError', 400)
}
}

export class AuthNoCookieError extends CustomAuthError {
constructor() {
super('No cooke found!', 'AuthNoCookieError', 401)
}
}

export class AuthInvalidCredentialsError extends CustomAuthError {
constructor(message: string) {
super(message, 'AuthInvalidCredentialsError', 400)
}
}
2 changes: 1 addition & 1 deletion src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type AuthResponse = {
error: AuthError
}

export type OAuthResposne = {
export type OAuthResponse = {
provider: Provider
url: string
error: null
Expand Down