Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
kangmingtay committed Aug 26, 2024
2 parents 3e7b435 + db41710 commit 274c94a
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 25 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [2.64.4](https://github.com/supabase/auth-js/compare/v2.64.3...v2.64.4) (2024-07-12)


### Bug Fixes

* update types ([#930](https://github.com/supabase/auth-js/issues/930)) ([dbc5962](https://github.com/supabase/auth-js/commit/dbc5962d609cc0470b5b03160f4cd8b9e7d03ce3))

## [2.64.3](https://github.com/supabase/auth-js/compare/v2.64.2...v2.64.3) (2024-06-17)


Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
"lint": "eslint ./src/**/* test/**/*.test.ts",
"test": "run-s test:clean test:infra test:suite test:clean",
"test:suite": "jest --runInBand",
"test:infra": "cd infra && docker-compose down && docker-compose pull && docker-compose up -d && sleep 30",
"test:clean": "cd infra && docker-compose down",
"test:infra": "cd infra && docker compose down && docker compose pull && docker compose up -d && sleep 30",
"test:clean": "cd infra && docker compose down",
"docs": "typedoc src/index.ts --out docs/v2 --excludePrivate --excludeProtected",
"docs:json": "typedoc --json docs/v2/spec.json --excludeExternals --excludePrivate --excludeProtected src/index.ts"
},
Expand Down
36 changes: 29 additions & 7 deletions src/GoTrueClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isAuthApiError,
isAuthError,
isAuthRetryableFetchError,
isAuthSessionMissingError,
} from './lib/errors'
import {
Fetch,
Expand Down Expand Up @@ -1172,6 +1173,15 @@ export default class GoTrueClient {
})
} catch (error) {
if (isAuthError(error)) {
if (isAuthSessionMissingError(error)) {
// JWT contains a `session_id` which does not correspond to an active
// session in the database, indicating the user is signed out.

await this._removeSession()
await removeItemAsync(this.storage, `${this.storageKey}-code-verifier`)
await this._notifyAllSubscribers('SIGNED_OUT', null)
}

return { data: { user: null }, error }
}

Expand Down Expand Up @@ -1465,7 +1475,7 @@ export default class GoTrueClient {
)
} else if (timeNow - issuedAt < 0) {
console.warn(
'@supabase/gotrue-js: Session as retrieved from URL was issued in the future? Check the device clok for skew',
'@supabase/gotrue-js: Session as retrieved from URL was issued in the future? Check the device clock for skew',
issuedAt,
expiresAt,
timeNow
Expand Down Expand Up @@ -2341,12 +2351,14 @@ export default class GoTrueClient {
return { data: null, error: sessionError }
}

const body = {
friendly_name: params.friendlyName,
factor_type: params.factorType,
...(params.factorType === 'phone' ? { phone: params.phone } : { issuer: params.issuer }),
}

const { data, error } = await _request(this.fetch, 'POST', `${this.url}/factors`, {
body: {
friendly_name: params.friendlyName,
factor_type: params.factorType,
issuer: params.issuer,
},
body,
headers: this.headers,
jwt: sessionData?.session?.access_token,
})
Expand All @@ -2355,7 +2367,12 @@ export default class GoTrueClient {
return { data: null, error }
}

if (data?.totp?.qr_code) {
// TODO: Remove once: https://github.com/supabase/auth/pull/1717 is deployed
if (params.factorType === 'phone') {
delete data.totp
}

if (params.factorType === 'totp' && data?.totp?.qr_code) {
data.totp.qr_code = `data:image/svg+xml;utf-8,${data.totp.qr_code}`
}

Expand Down Expand Up @@ -2429,6 +2446,7 @@ export default class GoTrueClient {
'POST',
`${this.url}/factors/${params.factorId}/challenge`,
{
body: { channel: params.channel },
headers: this.headers,
jwt: sessionData?.session?.access_token,
}
Expand Down Expand Up @@ -2483,11 +2501,15 @@ export default class GoTrueClient {
const totp = factors.filter(
(factor) => factor.factor_type === 'totp' && factor.status === 'verified'
)
const phone = factors.filter(
(factor) => factor.factor_type === 'phone' && factor.status === 'verified'
)

return {
data: {
all: factors,
totp,
phone,
},
error: null,
}
Expand Down
4 changes: 4 additions & 0 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export class AuthSessionMissingError extends CustomAuthError {
}
}

export function isAuthSessionMissingError(error: any): error is AuthSessionMissingError {
return isAuthError(error) && error.name === 'AuthSessionMissingError'
}

export class AuthInvalidTokenResponseError extends CustomAuthError {
constructor() {
super('Auth session or user missing', 'AuthInvalidTokenResponseError', 500, undefined)
Expand Down
6 changes: 6 additions & 0 deletions src/lib/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
AuthRetryableFetchError,
AuthWeakPasswordError,
AuthUnknownError,
AuthSessionMissingError,
} from './errors'

export type Fetch = typeof fetch
Expand Down Expand Up @@ -91,6 +92,11 @@ export async function handleError(error: unknown) {
error.status,
data.weak_password?.reasons || []
)
} else if (errorCode === 'session_not_found') {
// The `session_id` inside the JWT does not correspond to a row in the
// `sessions` table. This usually means the user has signed out, has been
// deleted, or their session has somehow been terminated.
throw new AuthSessionMissingError()
}

throw new AuthApiError(_getErrorMessage(data), error.status || 500, errorCode)
Expand Down
75 changes: 59 additions & 16 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,9 @@ export interface Factor {
friendly_name?: string

/**
* Type of factor. Only `totp` supported with this version but may change in
* future versions.
* Type of factor. `totp` and `phone` supported with this version
*/
factor_type: 'totp' | string
factor_type: 'totp' | 'phone' | string

/** Factor's status. */
status: 'verified' | 'unverified'
Expand Down Expand Up @@ -438,6 +437,22 @@ export interface AdminUserAttributes extends Omit<UserAttributes, 'data'> {
* Setting this role to `service_role` is not recommended as it grants the user admin privileges.
*/
role?: string

/**
* The `password_hash` for the user's password.
*
* Allows you to specify a password hash for the user. This is useful for migrating a user's password hash from another service.
*
* Supports bcrypt and argon2 password hashes.
*/
password_hash?: string

/**
* The `id` for the user.
*
* Allows you to overwrite the default `id` set for the user.
*/
id?: string
}

export interface Subscription {
Expand All @@ -455,10 +470,6 @@ export interface Subscription {
unsubscribe: () => void
}

export interface UpdatableFactorAttributes {
friendlyName: string
}

export type SignInAnonymouslyCredentials = {
options?: {
/**
Expand Down Expand Up @@ -789,14 +800,23 @@ export type GenerateLinkType =
| 'email_change_current'
| 'email_change_new'

export type MFAEnrollParams = {
/** The type of factor being enrolled. */
factorType: 'totp'
/** Domain which the user is enrolled with. */
issuer?: string
/** Human readable name assigned to the factor. */
friendlyName?: string
}
export type MFAEnrollParams =
| {
/** The type of factor being enrolled. */
factorType: 'totp'
/** Domain which the user is enrolled with. */
issuer?: string
/** Human readable name assigned to the factor. */
friendlyName?: string
}
| {
/** The type of factor being enrolled. */
factorType: 'phone'
/** Human readable name assigned to the factor. */
friendlyName?: string
/** Phone number associated with a factor. Number should conform to E.164 format */
phone: string
}

export type MFAUnenrollParams = {
/** ID of the factor being unenrolled. */
Expand All @@ -817,6 +837,8 @@ export type MFAVerifyParams = {
export type MFAChallengeParams = {
/** ID of the factor to be challenged. Returned in enroll(). */
factorId: string
/** Messaging channel to use (e.g. whatsapp or sms). Only relevant for phone factors */
channel?: 'sms' | 'whatsapp'
}

export type MFAChallengeAndVerifyParams = {
Expand Down Expand Up @@ -857,7 +879,7 @@ export type AuthMFAEnrollResponse =
/** ID of the factor that was just enrolled (in an unverified state). */
id: string

/** Type of MFA factor. Only `totp` supported for now. */
/** Type of MFA factor.*/
type: 'totp'

/** TOTP enrollment information. */
Expand All @@ -881,6 +903,22 @@ export type AuthMFAEnrollResponse =
}
error: null
}
| {
data: {
/** ID of the factor that was just enrolled (in an unverified state). */
id: string

/** Type of MFA factor. */
type: 'phone'

/** Friendly name of the factor, useful for distinguishing between factors **/
friendly_name?: string

/** Phone number of the MFA factor in E.164 format. Used to send messages */
phone: string
}
error: null
}
| {
data: null
error: AuthError
Expand All @@ -902,6 +940,9 @@ export type AuthMFAChallengeResponse =
/** ID of the newly created challenge. */
id: string

/** Factor Type which generated the challenge */
type: 'totp' | 'phone'

/** Timestamp in UNIX seconds when this challenge will no longer be usable. */
expires_at: number
}
Expand All @@ -917,6 +958,8 @@ export type AuthMFAListFactorsResponse =

/** Only verified TOTP factors. (A subset of `all`.) */
totp: Factor[]
/** Only verified Phone factors. (A subset of `all`.) */
phone: Factor[]
}
error: null
}
Expand Down

0 comments on commit 274c94a

Please sign in to comment.