353 lines
9.4 KiB
TypeScript
353 lines
9.4 KiB
TypeScript
import {
|
|
Fetch,
|
|
_generateLinkResponse,
|
|
_noResolveJsonResponse,
|
|
_request,
|
|
_userResponse,
|
|
} from './lib/fetch'
|
|
import { resolveFetch, validateUUID } from './lib/helpers'
|
|
import {
|
|
AdminUserAttributes,
|
|
GenerateLinkParams,
|
|
GenerateLinkResponse,
|
|
Pagination,
|
|
User,
|
|
UserResponse,
|
|
GoTrueAdminMFAApi,
|
|
AuthMFAAdminDeleteFactorParams,
|
|
AuthMFAAdminDeleteFactorResponse,
|
|
AuthMFAAdminListFactorsParams,
|
|
AuthMFAAdminListFactorsResponse,
|
|
PageParams,
|
|
SIGN_OUT_SCOPES,
|
|
SignOutScope,
|
|
} from './lib/types'
|
|
import { AuthError, isAuthError } from './lib/errors'
|
|
|
|
export default class GoTrueAdminApi {
|
|
/** Contains all MFA administration methods. */
|
|
mfa: GoTrueAdminMFAApi
|
|
|
|
protected url: string
|
|
protected headers: {
|
|
[key: string]: string
|
|
}
|
|
protected fetch: Fetch
|
|
|
|
constructor({
|
|
url = '',
|
|
headers = {},
|
|
fetch,
|
|
}: {
|
|
url: string
|
|
headers?: {
|
|
[key: string]: string
|
|
}
|
|
fetch?: Fetch
|
|
}) {
|
|
this.url = url
|
|
this.headers = headers
|
|
this.fetch = resolveFetch(fetch)
|
|
this.mfa = {
|
|
listFactors: this._listFactors.bind(this),
|
|
deleteFactor: this._deleteFactor.bind(this),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a logged-in session.
|
|
* @param jwt A valid, logged-in JWT.
|
|
* @param scope The logout sope.
|
|
*/
|
|
async signOut(
|
|
jwt: string,
|
|
scope: SignOutScope = SIGN_OUT_SCOPES[0]
|
|
): Promise<{ data: null; error: AuthError | null }> {
|
|
if (SIGN_OUT_SCOPES.indexOf(scope) < 0) {
|
|
throw new Error(
|
|
`@supabase/auth-js: Parameter scope must be one of ${SIGN_OUT_SCOPES.join(', ')}`
|
|
)
|
|
}
|
|
|
|
try {
|
|
await _request(this.fetch, 'POST', `${this.url}/logout?scope=${scope}`, {
|
|
headers: this.headers,
|
|
jwt,
|
|
noResolveJson: true,
|
|
})
|
|
return { data: null, error: null }
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return { data: null, error }
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends an invite link to an email address.
|
|
* @param email The email address of the user.
|
|
* @param options Additional options to be included when inviting.
|
|
*/
|
|
async inviteUserByEmail(
|
|
email: string,
|
|
options: {
|
|
/** A custom data object to store additional metadata about the user. This maps to the `auth.users.user_metadata` column. */
|
|
data?: object
|
|
|
|
/** The URL which will be appended to the email link sent to the user's email address. Once clicked the user will end up on this URL. */
|
|
redirectTo?: string
|
|
} = {}
|
|
): Promise<UserResponse> {
|
|
try {
|
|
return await _request(this.fetch, 'POST', `${this.url}/invite`, {
|
|
body: { email, data: options.data },
|
|
headers: this.headers,
|
|
redirectTo: options.redirectTo,
|
|
xform: _userResponse,
|
|
})
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return { data: { user: null }, error }
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates email links and OTPs to be sent via a custom email provider.
|
|
* @param email The user's email.
|
|
* @param options.password User password. For signup only.
|
|
* @param options.data Optional user metadata. For signup only.
|
|
* @param options.redirectTo The redirect url which should be appended to the generated link
|
|
*/
|
|
async generateLink(params: GenerateLinkParams): Promise<GenerateLinkResponse> {
|
|
try {
|
|
const { options, ...rest } = params
|
|
const body: any = { ...rest, ...options }
|
|
if ('newEmail' in rest) {
|
|
// replace newEmail with new_email in request body
|
|
body.new_email = rest?.newEmail
|
|
delete body['newEmail']
|
|
}
|
|
return await _request(this.fetch, 'POST', `${this.url}/admin/generate_link`, {
|
|
body: body,
|
|
headers: this.headers,
|
|
xform: _generateLinkResponse,
|
|
redirectTo: options?.redirectTo,
|
|
})
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return {
|
|
data: {
|
|
properties: null,
|
|
user: null,
|
|
},
|
|
error,
|
|
}
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// User Admin API
|
|
/**
|
|
* Creates a new user.
|
|
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
|
*/
|
|
async createUser(attributes: AdminUserAttributes): Promise<UserResponse> {
|
|
try {
|
|
return await _request(this.fetch, 'POST', `${this.url}/admin/users`, {
|
|
body: attributes,
|
|
headers: this.headers,
|
|
xform: _userResponse,
|
|
})
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return { data: { user: null }, error }
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a list of users.
|
|
*
|
|
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
|
* @param params An object which supports `page` and `perPage` as numbers, to alter the paginated results.
|
|
*/
|
|
async listUsers(
|
|
params?: PageParams
|
|
): Promise<
|
|
| { data: { users: User[]; aud: string } & Pagination; error: null }
|
|
| { data: { users: [] }; error: AuthError }
|
|
> {
|
|
try {
|
|
const pagination: Pagination = { nextPage: null, lastPage: 0, total: 0 }
|
|
const response = await _request(this.fetch, 'GET', `${this.url}/admin/users`, {
|
|
headers: this.headers,
|
|
noResolveJson: true,
|
|
query: {
|
|
page: params?.page?.toString() ?? '',
|
|
per_page: params?.perPage?.toString() ?? '',
|
|
},
|
|
xform: _noResolveJsonResponse,
|
|
})
|
|
if (response.error) throw response.error
|
|
|
|
const users = await response.json()
|
|
const total = response.headers.get('x-total-count') ?? 0
|
|
const links = response.headers.get('link')?.split(',') ?? []
|
|
if (links.length > 0) {
|
|
links.forEach((link: string) => {
|
|
const page = parseInt(link.split(';')[0].split('=')[1].substring(0, 1))
|
|
const rel = JSON.parse(link.split(';')[1].split('=')[1])
|
|
pagination[`${rel}Page`] = page
|
|
})
|
|
|
|
pagination.total = parseInt(total)
|
|
}
|
|
return { data: { ...users, ...pagination }, error: null }
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return { data: { users: [] }, error }
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get user by id.
|
|
*
|
|
* @param uid The user's unique identifier
|
|
*
|
|
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
|
*/
|
|
async getUserById(uid: string): Promise<UserResponse> {
|
|
validateUUID(uid)
|
|
|
|
try {
|
|
return await _request(this.fetch, 'GET', `${this.url}/admin/users/${uid}`, {
|
|
headers: this.headers,
|
|
xform: _userResponse,
|
|
})
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return { data: { user: null }, error }
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the user data.
|
|
*
|
|
* @param attributes The data you want to update.
|
|
*
|
|
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
|
*/
|
|
async updateUserById(uid: string, attributes: AdminUserAttributes): Promise<UserResponse> {
|
|
validateUUID(uid)
|
|
|
|
try {
|
|
return await _request(this.fetch, 'PUT', `${this.url}/admin/users/${uid}`, {
|
|
body: attributes,
|
|
headers: this.headers,
|
|
xform: _userResponse,
|
|
})
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return { data: { user: null }, error }
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a user. Requires a `service_role` key.
|
|
*
|
|
* @param id The user id you want to remove.
|
|
* @param shouldSoftDelete If true, then the user will be soft-deleted from the auth schema. Soft deletion allows user identification from the hashed user ID but is not reversible.
|
|
* Defaults to false for backward compatibility.
|
|
*
|
|
* This function should only be called on a server. Never expose your `service_role` key in the browser.
|
|
*/
|
|
async deleteUser(id: string, shouldSoftDelete = false): Promise<UserResponse> {
|
|
validateUUID(id)
|
|
|
|
try {
|
|
return await _request(this.fetch, 'DELETE', `${this.url}/admin/users/${id}`, {
|
|
headers: this.headers,
|
|
body: {
|
|
should_soft_delete: shouldSoftDelete,
|
|
},
|
|
xform: _userResponse,
|
|
})
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return { data: { user: null }, error }
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
private async _listFactors(
|
|
params: AuthMFAAdminListFactorsParams
|
|
): Promise<AuthMFAAdminListFactorsResponse> {
|
|
validateUUID(params.userId)
|
|
|
|
try {
|
|
const { data, error } = await _request(
|
|
this.fetch,
|
|
'GET',
|
|
`${this.url}/admin/users/${params.userId}/factors`,
|
|
{
|
|
headers: this.headers,
|
|
xform: (factors: any) => {
|
|
return { data: { factors }, error: null }
|
|
},
|
|
}
|
|
)
|
|
return { data, error }
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return { data: null, error }
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
private async _deleteFactor(
|
|
params: AuthMFAAdminDeleteFactorParams
|
|
): Promise<AuthMFAAdminDeleteFactorResponse> {
|
|
validateUUID(params.userId)
|
|
validateUUID(params.id)
|
|
|
|
try {
|
|
const data = await _request(
|
|
this.fetch,
|
|
'DELETE',
|
|
`${this.url}/admin/users/${params.userId}/factors/${params.id}`,
|
|
{
|
|
headers: this.headers,
|
|
}
|
|
)
|
|
|
|
return { data, error: null }
|
|
} catch (error) {
|
|
if (isAuthError(error)) {
|
|
return { data: null, error }
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
}
|