Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/real-snakes-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@genseki/plugins": minor
---

Implement auto login after sign up for phone plugin
42 changes: 32 additions & 10 deletions packages/plugins/src/phone/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import z from 'zod'

import type { AnyContextable, ObjectWithOnlyValue } from '@genseki/react'
import { createEndpoint, createPlugin, HttpUnauthorizedError, ResponseHelper } from '@genseki/react'
import {
createEndpoint,
createPlugin,
HttpInternalServerError,
HttpUnauthorizedError,
ResponseHelper,
} from '@genseki/react'

import type { PhoneService } from './service'
import type { PhoneStore } from './store'
Expand Down Expand Up @@ -53,12 +59,16 @@ export function phone<
}
)

const signUpBody = service.getOptions().signUp.body as ReturnType<
TPhoneService['getOptions']
>['signUp']['body']

const sendSignUpOtpEndpoint = createEndpoint(
context,
{
method: 'POST',
path: '/auth/phone/sign-up/otp',
body: service.signUpBody as TPhoneService['signUpBody'],
body: signUpBody,
responses: {
200: z.object({
token: z.string(),
Expand Down Expand Up @@ -113,6 +123,7 @@ export function phone<
}),
responses: {
200: z.object({
userId: z.string(),
message: z.string(),
}),
500: z.union([
Expand All @@ -134,21 +145,32 @@ export function phone<
]),
},
},
async (payload) => {
async (payload, { response }) => {
const body = payload.body

const response = await service.verifySignUpPhoneOtp(body)
const result = await service.verifySignUpPhoneOtp(body)

if (response.isErr()) {
return {
status: 500,
body: response.error,
}
if (result.isErr()) {
return { status: 500, body: result.error }
}

const session = await service.createSession(result.value.userId)
if (session.isErr()) {
throw new HttpInternalServerError(session.error.message)
}

if (service.getOptions().signUp.autoLogin !== false) {
ResponseHelper.setSessionCookie(response, session.value.token, {
expires: session.value.expiredAt,
})
}

return {
status: 200,
body: { message: 'Sign up success' },
body: {
userId: result.value.userId,
message: 'Sign up success',
},
}
}
)
Expand Down
35 changes: 25 additions & 10 deletions packages/plugins/src/phone/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface PhoneServiceOptions<
}
signUp: {
body: TSignUpBodySchema
autoLogin?: boolean // default to true
onOtpSent: (data: {
phone: string
name: string
Expand Down Expand Up @@ -56,29 +57,39 @@ const defaultOptions = {
},
} satisfies PartialDeep<PhoneServiceOptions>

export type PhoneServiceOptionsWithDefaults = ReturnType<
typeof defu<PhoneServiceOptions, [typeof defaultOptions]>
>
export type PhoneServiceOptionsWithDefaults<
TSignUpBodySchema extends BaseSignUpBodySchema = BaseSignUpBodySchema,
> = ReturnType<typeof defu<PhoneServiceOptions<TSignUpBodySchema>, [typeof defaultOptions]>>

export class PhoneService<
TContext extends AnyContextable,
TSchema extends ObjectWithOnlyValue<PluginSchema, any>,
TSignUpBodySchema extends BaseSignUpBodySchema,
TStore extends PhoneStore<TSignUpBodySchema>,
> {
private readonly options: PhoneServiceOptionsWithDefaults
private readonly options: PhoneServiceOptionsWithDefaults<TSignUpBodySchema>

constructor(
public readonly context: TContext,
public readonly schema: TSchema,
options: ValidateSchema<PluginSchema, TSchema, PhoneServiceOptions<TSignUpBodySchema>>,
private readonly store: TStore
) {
this.options = defu(options, defaultOptions) as PhoneServiceOptionsWithDefaults
this.options = defu(
options,
defaultOptions
) as PhoneServiceOptionsWithDefaults<TSignUpBodySchema>
}

getOptions(): PhoneServiceOptionsWithDefaults<TSignUpBodySchema> {
return this.options
}

get signUpBody(): TSignUpBodySchema {
return this.options.signUp.body as TSignUpBodySchema
async createSession(userId: string) {
return this.store
.createSession(userId)
.then((result) => ok(result))
.catch((error) => err(error))
}

async login(body: { phone: string; password: string }) {
Expand All @@ -104,9 +115,13 @@ export class PhoneService<
return err({ message: 'Invalid password' })
}

const session = await this.store.createSession(user.id)
const session = await this.createSession(user.id)

if (session.isErr()) {
return err({ message: 'Failed to create session', cause: session.error })
}

return ok(session)
return ok(session.value)
}

async sendSignUpPhoneOtp(data: z.output<TSignUpBodySchema>) {
Expand Down Expand Up @@ -249,7 +264,7 @@ export class PhoneService<
const userId = await this.store.createUser(verification.value.data)
await this.store.createAccount(userId, verification.value.password)

return ok(null)
return ok({ userId })
}

async sendChangePhoneNumberOtp(userId: string, phone: { new: string; old: string }) {
Expand Down
Loading