Skip to content

Commit

Permalink
feat: add bindings for Multi-Factor Authentication (Phone) (#932)
Browse files Browse the repository at this point in the history
## What kind of change does this PR introduce?

Adds the bindings for MFA (Phone) Client library. In particular, add
- Enroll - now allows for enrollment of Phone Factors
- Challenge for Phone - now takes in a channel
- List Factors - now supports access of Phone factors


### TODOS:
- [x] Remove empty `totp` from response field  on enroll response

### Not addressed yet

- `challengeAndVerify` will currently return an error on the `verify`
step when used with a phone factor. We could introduce an alternate
behaviour (e.g. terminating at the `challenge` step gracefully) since we
have the `type` of the challenge-associated-factor in the response. This
is not addressed in this PR
  • Loading branch information
J0 authored Aug 9, 2024
1 parent 38eef89 commit b957c30
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 22 deletions.
24 changes: 18 additions & 6 deletions src/GoTrueClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2341,12 +2341,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 +2357,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 +2436,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 +2491,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
59 changes: 43 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 @@ -471,10 +470,6 @@ export interface Subscription {
unsubscribe: () => void
}

export interface UpdatableFactorAttributes {
friendlyName: string
}

export type SignInAnonymouslyCredentials = {
options?: {
/**
Expand Down Expand Up @@ -805,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 @@ -833,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 @@ -873,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 @@ -897,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 @@ -918,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 @@ -933,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 b957c30

Please sign in to comment.