B2B-88: add starter kit structure and elements

This commit is contained in:
devmc-ee
2025-06-08 16:18:30 +03:00
parent 657a36a298
commit e7b25600cb
1280 changed files with 77893 additions and 5688 deletions

3
packages/otp/README.md Normal file
View File

@@ -0,0 +1,3 @@
# One-Time Password (OTP) - @kit/otp
This package provides a service for working with one-time passwords and tokens in Supabase.

View File

@@ -0,0 +1,3 @@
import baseConfig from '@kit/eslint-config/base.js';
export default baseConfig;

1
packages/otp/node_modules/@hookform/resolvers generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../node_modules/.pnpm/@hookform+resolvers@5.0.1_react-hook-form@7.57.0_react@19.1.0_/node_modules/@hookform/resolvers

1
packages/otp/node_modules/@kit/email-templates generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../email-templates

1
packages/otp/node_modules/@kit/eslint-config generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../tooling/eslint

1
packages/otp/node_modules/@kit/mailers generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../mailers/core

1
packages/otp/node_modules/@kit/next generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../next

1
packages/otp/node_modules/@kit/prettier-config generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../tooling/prettier

1
packages/otp/node_modules/@kit/shared generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../shared

1
packages/otp/node_modules/@kit/supabase generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../supabase

1
packages/otp/node_modules/@kit/tsconfig generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../tooling/typescript

1
packages/otp/node_modules/@kit/ui generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../ui

1
packages/otp/node_modules/@radix-ui/react-icons generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../node_modules/.pnpm/@radix-ui+react-icons@1.3.2_react@19.1.0/node_modules/@radix-ui/react-icons

1
packages/otp/node_modules/@supabase/supabase-js generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../node_modules/.pnpm/@supabase+supabase-js@2.49.4/node_modules/@supabase/supabase-js

1
packages/otp/node_modules/@types/react generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../node_modules/.pnpm/@types+react@19.1.4/node_modules/@types/react

1
packages/otp/node_modules/@types/react-dom generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../node_modules/.pnpm/@types+react-dom@19.1.5_@types+react@19.1.4/node_modules/@types/react-dom

1
packages/otp/node_modules/react generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../node_modules/.pnpm/react@19.1.0/node_modules/react

1
packages/otp/node_modules/react-dom generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../node_modules/.pnpm/react-dom@19.1.0_react@19.1.0/node_modules/react-dom

1
packages/otp/node_modules/react-hook-form generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../node_modules/.pnpm/react-hook-form@7.57.0_react@19.1.0/node_modules/react-hook-form

1
packages/otp/node_modules/zod generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../node_modules/.pnpm/zod@3.25.56/node_modules/zod

43
packages/otp/package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "@kit/otp",
"private": true,
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/api/index.ts",
"./components": "./src/components/index.ts"
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@kit/email-templates": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/mailers": "workspace:*",
"@kit/next": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "2.49.4",
"@types/react": "19.1.4",
"@types/react-dom": "19.1.5",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-hook-form": "^7.56.3",
"zod": "^3.24.4"
},
"typesVersions": {
"*": {
"*": [
"src/*"
]
}
}
}

View File

@@ -0,0 +1,117 @@
/**
* @file API for one-time passwords/tokens
*
* Usage
*
* ```typescript
* import { createOtpApi } from '@kit/otp/api';
* import { getSupabaseServerClient } from '@kit/supabase/server-client';
* import { NoncePurpose } from '@kit/otp/types';
*
* const client = getSupabaseServerClient();
* const api = createOtpApi(client);
*
* // Create a one-time password token
* const { token } = await api.createToken({
* userId: user.id,
* purpose: NoncePurpose.PASSWORD_RESET, // Or use a custom string like 'password-reset'
* expiresInSeconds: 3600, // 1 hour
* metadata: { redirectTo: '/reset-password' },
* });
*
* // Verify a token
* const result = await api.verifyToken({
* token: '...',
* purpose: NoncePurpose.PASSWORD_RESET, // Must match the purpose used when creating
* });
*
* if (result.valid) {
* // Token is valid
* const { userId, metadata } = result;
* // Proceed with the operation
* } else {
* // Token is invalid or expired
* }
* ```
*/
import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@kit/supabase/database';
import { createOtpEmailService } from '../server/otp-email.service';
import { createOtpService } from '../server/otp.service';
import {
CreateNonceParams,
GetNonceStatusParams,
RevokeNonceParams,
SendOtpEmailParams,
VerifyNonceParams,
} from '../types';
/**
* @name createOtpApi
* @description Create an instance of the OTP API
* @param client
*/
export function createOtpApi(client: SupabaseClient<Database>) {
return new OtpApi(client);
}
/**
* @name OtpApi
* @description API for working with one-time tokens/passwords
*/
class OtpApi {
private readonly service: ReturnType<typeof createOtpService>;
private readonly emailService: ReturnType<typeof createOtpEmailService>;
constructor(client: SupabaseClient<Database>) {
this.service = createOtpService(client);
this.emailService = createOtpEmailService();
}
/**
* @name sendOtpEmail
* @description Sends an OTP email to the user
* @param params
*/
sendOtpEmail(params: SendOtpEmailParams) {
return this.emailService.sendOtpEmail(params);
}
/**
* @name createToken
* @description Creates a new one-time token
* @param params
*/
createToken(params: CreateNonceParams) {
return this.service.createNonce(params);
}
/**
* @name verifyToken
* @description Verifies a one-time token
* @param params
*/
verifyToken(params: VerifyNonceParams) {
return this.service.verifyNonce(params);
}
/**
* @name revokeToken
* @description Revokes a one-time token to prevent its use
* @param params
*/
revokeToken(params: RevokeNonceParams) {
return this.service.revokeNonce(params);
}
/**
* @name getTokenStatus
* @description Gets the status of a one-time token
* @param params
*/
getTokenStatus(params: GetNonceStatusParams) {
return this.service.getNonceStatus(params);
}
}

View File

@@ -0,0 +1 @@
export { VerifyOtpForm } from './verify-otp-form';

View File

@@ -0,0 +1,257 @@
'use client';
import { useState, useTransition } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Button } from '@kit/ui/button';
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormMessage,
} from '@kit/ui/form';
import { If } from '@kit/ui/if';
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from '@kit/ui/input-otp';
import { Spinner } from '@kit/ui/spinner';
import { Trans } from '@kit/ui/trans';
import { sendOtpEmailAction } from '../server/server-actions';
// Email form schema
const SendOtpSchema = z.object({
email: z.string().email({ message: 'Please enter a valid email address' }),
});
// OTP verification schema
const VerifyOtpSchema = z.object({
otp: z.string().min(6, { message: 'Please enter a valid OTP code' }).max(6),
});
type VerifyOtpFormProps = {
// Purpose of the OTP (e.g., 'email-verification', 'password-reset')
purpose: string;
// Callback when OTP is successfully verified
onSuccess: (otp: string) => void;
// Email address to send the OTP to
email: string;
// Customize form appearance
className?: string;
// Optional cancel button
CancelButton?: React.ReactNode;
};
export function VerifyOtpForm({
purpose,
email,
className,
CancelButton,
onSuccess,
}: VerifyOtpFormProps) {
// Track the current step (email entry or OTP verification)
const [step, setStep] = useState<'email' | 'otp'>('email');
const [isPending, startTransition] = useTransition();
// Track errors
const [error, setError] = useState<string | null>(null);
// Track verification success
const [, setVerificationSuccess] = useState(false);
// Email form
const emailForm = useForm<z.infer<typeof SendOtpSchema>>({
resolver: zodResolver(SendOtpSchema),
defaultValues: {
email,
},
});
// OTP verification form
const otpForm = useForm<z.infer<typeof VerifyOtpSchema>>({
resolver: zodResolver(VerifyOtpSchema),
defaultValues: {
otp: '',
},
});
// Handle sending OTP email
const handleSendOtp = () => {
setError(null);
startTransition(async () => {
try {
const result = await sendOtpEmailAction({
purpose,
email,
});
if (result.success) {
setStep('otp');
} else {
setError(result.error || 'Failed to send OTP. Please try again.');
}
} catch (err) {
setError('An unexpected error occurred. Please try again.');
console.error('Error sending OTP:', err);
}
});
};
// Handle OTP verification
const handleVerifyOtp = (data: z.infer<typeof VerifyOtpSchema>) => {
setVerificationSuccess(true);
onSuccess(data.otp);
};
return (
<div className={className}>
{step === 'email' ? (
<Form {...emailForm}>
<form
className="flex flex-col gap-y-8"
onSubmit={emailForm.handleSubmit(handleSendOtp)}
>
<div className="flex flex-col gap-y-2">
<p className="text-muted-foreground text-sm">
<Trans
i18nKey="common:otp.requestVerificationCodeDescription"
values={{ email }}
/>
</p>
</div>
<If condition={Boolean(error)}>
<Alert variant="destructive">
<ExclamationTriangleIcon className="h-4 w-4" />
<AlertTitle>
<Trans i18nKey="common:otp.errorSendingCode" />
</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
</If>
<div className="flex w-full justify-end gap-2">
{CancelButton}
<Button
type="submit"
disabled={isPending}
data-test="otp-send-verification-button"
>
{isPending ? (
<>
<Spinner className="mr-2 h-4 w-4" />
<Trans i18nKey="common:otp.sendingCode" />
</>
) : (
<Trans i18nKey="common:otp.sendVerificationCode" />
)}
</Button>
</div>
</form>
</Form>
) : (
<Form {...otpForm}>
<div className="flex w-full flex-col items-center gap-y-8">
<div className="text-muted-foreground text-sm">
<Trans i18nKey="common:otp.codeSentToEmail" values={{ email }} />
</div>
<form
className="flex w-full flex-col items-center space-y-8"
onSubmit={otpForm.handleSubmit(handleVerifyOtp)}
>
<If condition={Boolean(error)}>
<Alert variant="destructive">
<ExclamationTriangleIcon className="h-4 w-4" />
<AlertTitle>
<Trans i18nKey="common:error" />
</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
</If>
<FormField
name="otp"
control={otpForm.control}
render={({ field }) => (
<FormItem>
<FormControl>
<InputOTP
maxLength={6}
{...field}
disabled={isPending}
data-test="otp-input"
>
<InputOTPGroup>
<InputOTPSlot index={0} data-slot="0" />
<InputOTPSlot index={1} data-slot="1" />
<InputOTPSlot index={2} data-slot="2" />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} data-slot="3" />
<InputOTPSlot index={4} data-slot="4" />
<InputOTPSlot index={5} data-slot="5" />
</InputOTPGroup>
</InputOTP>
</FormControl>
<FormDescription>
<Trans i18nKey="common:otp.enterCodeFromEmail" />
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="flex w-full justify-between gap-2">
{CancelButton}
<div className="flex justify-end gap-2">
<Button
type="button"
variant="ghost"
disabled={isPending}
onClick={() => setStep('email')}
>
<Trans i18nKey="common:otp.requestNewCode" />
</Button>
<Button
type="submit"
disabled={isPending}
data-test="otp-verify-button"
>
{isPending ? (
<>
<Spinner className="mr-2 h-4 w-4" />
<Trans i18nKey="common:otp.verifying" />
</>
) : (
<Trans i18nKey="common:otp.verifyCode" />
)}
</Button>
</div>
</div>
</form>
</div>
</Form>
)}
</div>
);
}

View File

@@ -0,0 +1 @@
export * from './otp.service';

View File

@@ -0,0 +1,62 @@
import { z } from 'zod';
import { renderOtpEmail } from '@kit/email-templates';
import { getMailer } from '@kit/mailers';
import { getLogger } from '@kit/shared/logger';
const EMAIL_SENDER = z
.string({
required_error: 'EMAIL_SENDER is required',
})
.min(1)
.parse(process.env.EMAIL_SENDER);
const PRODUCT_NAME = z
.string({
required_error: 'PRODUCT_NAME is required',
})
.min(1)
.parse(process.env.NEXT_PUBLIC_PRODUCT_NAME);
/**
* @name createOtpEmailService
* @description Creates a new OtpEmailService
* @returns {OtpEmailService}
*/
export function createOtpEmailService() {
return new OtpEmailService();
}
/**
* @name OtpEmailService
* @description Service for sending OTP emails
*/
class OtpEmailService {
async sendOtpEmail(params: { email: string; otp: string }) {
const logger = await getLogger();
const { email, otp } = params;
const mailer = await getMailer();
const { html, subject } = await renderOtpEmail({
otp,
productName: PRODUCT_NAME,
});
try {
logger.info({ otp }, 'Sending OTP email...');
await mailer.sendEmail({
to: email,
subject,
html,
from: EMAIL_SENDER,
});
logger.info({ otp }, 'OTP email sent');
} catch (error) {
logger.error({ otp, error }, 'Error sending OTP email');
throw error;
}
}
}

View File

@@ -0,0 +1,275 @@
import 'server-only';
import { SupabaseClient } from '@supabase/supabase-js';
import { getLogger } from '@kit/shared/logger';
import { Database, Json } from '@kit/supabase/database';
import {
CreateNonceParams,
CreateNonceResult,
GetNonceStatusParams,
GetNonceStatusResult,
RevokeNonceParams,
VerifyNonceParams,
VerifyNonceResult,
} from '../types';
/**
* @name createOtpService
* @description Creates an instance of the OtpService
* @param client
*/
export function createOtpService(client: SupabaseClient<Database>) {
return new OtpService(client);
}
// Type declarations for RPC parameters
type CreateNonceRpcParams = {
p_user_id?: string;
p_purpose?: string;
p_expires_in_seconds?: number;
p_metadata?: Json;
p_description?: string;
p_tags?: string[];
p_scopes?: string[];
p_revoke_previous?: boolean;
};
type VerifyNonceRpcParams = {
p_token: string;
p_purpose: string;
p_required_scopes?: string[];
p_max_verification_attempts?: number;
};
/**
* @name OtpService
* @description Service for creating and verifying one-time tokens/passwords
*/
class OtpService {
constructor(private readonly client: SupabaseClient<Database>) {}
/**
* @name createNonce
* @description Creates a new one-time token for a user
* @param params
*/
async createNonce(params: CreateNonceParams) {
const logger = await getLogger();
const {
userId,
purpose,
expiresInSeconds = 900,
metadata = {},
description,
tags,
scopes,
revokePrevious = true,
} = params;
const ctx = { userId, purpose, name: 'nonce' };
logger.info(ctx, 'Creating one-time token');
try {
const result = await this.client.rpc('create_nonce', {
p_user_id: userId,
p_purpose: purpose,
p_expires_in_seconds: expiresInSeconds,
p_metadata: metadata as Json,
p_description: description,
p_tags: tags,
p_scopes: scopes,
p_revoke_previous: revokePrevious,
} as CreateNonceRpcParams);
if (result.error) {
logger.error(
{ ...ctx, error: result.error.message },
'Failed to create one-time token',
);
throw new Error(
`Failed to create one-time token: ${result.error.message}`,
);
}
const data = result.data as unknown as CreateNonceResult;
logger.info(
{ ...ctx, revokedPreviousCount: data.revoked_previous_count },
'One-time token created successfully',
);
return {
id: data.id,
token: data.token,
expiresAt: data.expires_at,
revokedPreviousCount: data.revoked_previous_count,
};
} catch (error) {
logger.error({ ...ctx, error }, 'Error creating one-time token');
throw error;
}
}
/**
* @name verifyNonce
* @description Verifies a one-time token
* @param params
*/
async verifyNonce(params: VerifyNonceParams) {
const logger = await getLogger();
const {
token,
purpose,
requiredScopes,
maxVerificationAttempts = 1,
} = params;
const ctx = { purpose, name: 'verify-nonce' };
logger.info(ctx, 'Verifying one-time token');
try {
const result = await this.client.rpc('verify_nonce', {
p_token: token,
p_user_id: params.userId,
p_purpose: purpose,
p_required_scopes: requiredScopes,
p_max_verification_attempts: maxVerificationAttempts,
} as VerifyNonceRpcParams);
if (result.error) {
logger.error(
{ ...ctx, error: result.error.message },
'Failed to verify one-time token',
);
throw new Error(
`Failed to verify one-time token: ${result.error.message}`,
);
}
const data = result.data as unknown as VerifyNonceResult;
logger.info(
{
...ctx,
...data,
},
'One-time token verification complete',
);
return data;
} catch (error) {
logger.error({ ...ctx, error }, 'Error verifying one-time token');
throw error;
}
}
/**
* @name revokeNonce
* @description Revokes a one-time token to prevent its use
* @param params
*/
async revokeNonce(params: RevokeNonceParams) {
const logger = await getLogger();
const { id, reason } = params;
const ctx = { id, reason, name: 'revoke-nonce' };
logger.info(ctx, 'Revoking one-time token');
try {
const { data, error } = await this.client.rpc('revoke_nonce', {
p_id: id,
p_reason: reason,
});
if (error) {
logger.error(
{ ...ctx, error: error.message },
'Failed to revoke one-time token',
);
throw new Error(`Failed to revoke one-time token: ${error.message}`);
}
logger.info(
{ ...ctx, success: data },
'One-time token revocation complete',
);
return {
success: data,
};
} catch (error) {
logger.error({ ...ctx, error }, 'Error revoking one-time token');
throw error;
}
}
/**
* @name getNonceStatus
* @description Gets the status of a one-time token
* @param params
*/
async getNonceStatus(params: GetNonceStatusParams) {
const logger = await getLogger();
const { id } = params;
const ctx = { id, name: 'get-nonce-status' };
logger.info(ctx, 'Getting one-time token status');
try {
const result = await this.client.rpc('get_nonce_status', {
p_id: id,
});
if (result.error) {
logger.error(
{ ...ctx, error: result.error.message },
'Failed to get one-time token status',
);
throw new Error(
`Failed to get one-time token status: ${result.error.message}`,
);
}
const data = result.data as unknown as GetNonceStatusResult;
logger.info(
{ ...ctx, exists: data.exists },
'Retrieved one-time token status',
);
if (!data.exists) {
return {
exists: false,
};
}
return {
exists: data.exists,
purpose: data.purpose,
userId: data.user_id,
createdAt: data.created_at,
expiresAt: data.expires_at,
usedAt: data.used_at,
revoked: data.revoked,
revokedReason: data.revoked_reason,
verificationAttempts: data.verification_attempts,
lastVerificationAt: data.last_verification_at,
lastVerificationIp: data.last_verification_ip,
isValid: data.is_valid,
};
} catch (error) {
logger.error({ ...ctx, error }, 'Error getting one-time token status');
throw error;
}
}
}

View File

@@ -0,0 +1,95 @@
'use server';
import { z } from 'zod';
import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { createOtpApi } from '../api';
// Schema for sending OTP email
const SendOtpEmailSchema = z.object({
email: z.string().email({ message: 'Please enter a valid email address' }),
// Purpose of the OTP (e.g., 'email-verification', 'password-reset')
purpose: z.string().min(1).max(1000),
// how long the OTP should be valid for. Defaults to 1 hour. Max is 7 days. Min is 30 seconds.
expiresInSeconds: z
.number()
.min(30)
.max(86400 * 7)
.default(3600)
.optional(),
});
/**
* Server action to generate an OTP and send it via email
*/
export const sendOtpEmailAction = enhanceAction(
async function (data: z.infer<typeof SendOtpEmailSchema>, user) {
const logger = await getLogger();
const ctx = { name: 'send-otp-email', userId: user.id };
const email = user.email;
// validate edge case where user has no email
if (!email) {
throw new Error('User has no email. OTP verification is not possible.');
}
// validate edge case where email is not the same as the one provided
// this is highly unlikely to happen, but we want to make sure the client-side code is correct in
// sending the correct user email
if (data.email !== email) {
throw new Error(
'User email does not match the email provided. This is likely an error in the client.',
);
}
try {
const { purpose, expiresInSeconds } = data;
logger.info(
{ ...ctx, email, purpose },
'Creating OTP token and sending email',
);
const client = getSupabaseServerAdminClient();
const otpApi = createOtpApi(client);
// Create a token that will be verified later
const tokenResult = await otpApi.createToken({
userId: user.id,
purpose,
expiresInSeconds,
});
// Send the email with the OTP
await otpApi.sendOtpEmail({
email,
otp: tokenResult.token,
});
logger.info(
{ ...ctx, tokenId: tokenResult.id },
'OTP email sent successfully',
);
return {
success: true,
tokenId: tokenResult.id,
};
} catch (error) {
logger.error({ ...ctx, error }, 'Failed to send OTP email');
return {
success: false,
error:
error instanceof Error ? error.message : 'Failed to send OTP email',
};
}
},
{
schema: SendOtpEmailSchema,
auth: true,
},
);

View File

@@ -0,0 +1,115 @@
/**
* @name CreateNonceParams - Parameters for creating a nonce
*/
export interface CreateNonceParams {
userId?: string;
purpose: string;
expiresInSeconds?: number;
metadata?: Record<string, unknown>;
description?: string;
tags?: string[];
scopes?: string[];
revokePrevious?: boolean;
}
/**
* @name VerifyNonceParams - Parameters for verifying a nonce
*/
export interface VerifyNonceParams {
token: string;
purpose: string;
userId?: string;
requiredScopes?: string[];
maxVerificationAttempts?: number;
}
/**
* @name RevokeNonceParams - Parameters for revoking a nonce
*/
export interface RevokeNonceParams {
id: string;
reason?: string;
}
/**
* @name CreateNonceResult - Result of creating a nonce
*/
export interface CreateNonceResult {
id: string;
token: string;
expires_at: string;
revoked_previous_count?: number;
}
/**
* @name ValidNonceResult - Result of verifying a nonce
*/
type ValidNonceResult = {
valid: boolean;
user_id?: string;
metadata?: Record<string, unknown>;
message?: string;
scopes?: string[];
purpose?: string;
};
/**
* @name InvalidNonceResult - Result of verifying a nonce
*/
type InvalidNonceResult = {
valid: false;
message: string;
max_attempts_exceeded?: boolean;
};
/**
* @name VerifyNonceResult - Result of verifying a nonce
*/
export type VerifyNonceResult = ValidNonceResult | InvalidNonceResult;
/**
* @name GetNonceStatusParams - Parameters for getting nonce status
*/
export interface GetNonceStatusParams {
id: string;
}
/**
* @name SuccessGetNonceStatusResult - Result of getting nonce status
*/
type SuccessGetNonceStatusResult = {
exists: true;
purpose?: string;
user_id?: string;
created_at?: string;
expires_at?: string;
used_at?: string;
revoked?: boolean;
revoked_reason?: string;
verification_attempts?: number;
last_verification_at?: string;
last_verification_ip?: string;
is_valid?: boolean;
};
/**
* @name FailedGetNonceStatusResult - Result of getting nonce status
*/
type FailedGetNonceStatusResult = {
exists: false;
};
/**
* @name GetNonceStatusResult - Result of getting nonce status
*/
export type GetNonceStatusResult =
| SuccessGetNonceStatusResult
| FailedGetNonceStatusResult;
/**
* @name SendOtpEmailParams - Parameters for sending an OTP email
*/
export interface SendOtpEmailParams {
email: string;
otp: string;
}

View File

@@ -0,0 +1,8 @@
{
"extends": "@kit/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}