Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enh(#843): Allow signup flow return data when preventLoginFlow is true #903

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion playground-local/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export default defineNuxtConfig({
provider: {
type: 'local',
endpoints: {
getSession: { path: '/user' }
getSession: { path: '/user' },
signUp: { path: '/signup', method: 'post' }
},
pages: {
login: '/'
Expand Down
4 changes: 4 additions & 0 deletions playground-local/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ definePageMeta({ auth: false })
-> manual login, logout, refresh button
</nuxt-link>
<br>
<nuxt-link to="/register">
-> Click to signup
</nuxt-link>
<br>
<nuxt-link to="/protected/globally">
-> globally protected page
</nuxt-link>
Expand Down
45 changes: 45 additions & 0 deletions playground-local/pages/register.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script setup>
import { ref } from 'vue'
import { definePageMeta, useAuth } from '#imports'

const { signUp } = useAuth()

const username = ref('')
const password = ref('')
const response = ref()

async function register() {
try {
const signUpResponse = await signUp({ username: username.value, password: password.value }, undefined, { preventLoginFlow: true })
response.value = signUpResponse
}
catch (error) {
response.value = { error: 'Failed to sign up' }
console.error(error)
}
}

definePageMeta({
auth: {
unauthenticatedOnly: true,
navigateAuthenticatedTo: '/',
},
})
</script>

<template>
<div>
<form @submit.prevent="register">
<p><i>*password should have at least 6 characters</i></p>
<input v-model="username" type="text" placeholder="Username" data-testid="username">
<input v-model="password" type="password" placeholder="Password" data-testid="password">
<button type="submit" data-testid="submit">
sign up
</button>
</form>
<div v-if="response">
<h2>Response</h2>
<pre>{{ response }}</pre>
</div>
</div>
</template>
40 changes: 40 additions & 0 deletions playground-local/server/api/auth/signup.post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createError, eventHandler, readBody } from 'h3'
import { z } from 'zod'
import { sign } from 'jsonwebtoken'

export const SECRET = 'dummy'

export default eventHandler(async (event) => {
// Define the schema for validating the incoming data
const result = z.object({
username: z.string(),
password: z.string().min(6)
}).safeParse(await readBody(event))

// If validation fails, return an error
if (!result.success) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid input, please provide a valid email and a password of at least 6 characters.'
})
}

const { username } = result.data

const expiresIn = '1h' // token expiry (1 hour)
const user = { username } // Payload for the token, includes the email

// Sign the JWT with the user payload and secret
const accessToken = sign(user, SECRET, { expiresIn })

// Return a success response with the email and the token
return {
message: 'Signup successful!',
user: {
username
},
token: {
accessToken
}
}
})
26 changes: 26 additions & 0 deletions playground-local/tests/local.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,30 @@
await signoutButton.click()
await playwrightExpect(status).toHaveText(STATUS_UNAUTHENTICATED)
})

it('should sign up and return signup data when preventLoginFlow: true', async () => {
const page = await createPage('/register') // Navigate to signup page

const [
usernameInput,
passwordInput,
submitButton,
] = await Promise.all([
page.getByTestId('username'),
page.getByTestId('password'),
page.getByTestId('submit')
])

await usernameInput.fill('newuser')

Check failure on line 76 in playground-local/tests/local.spec.ts

View workflow job for this annotation

GitHub Actions / test-playground-local

tests/local.spec.ts > local Provider > should sign up and return signup data when preventLoginFlow: true

Error: locator.fill: Error: strict mode violation: getByTestId('username') resolved to 2 elements: 1) <input type="text" value="smith" placeholder="Username" data-testid="username"/> aka locator('form').filter({ hasText: 'sign in' }).getByTestId('username') 2) <input value="" type="text" placeholder="Username" data-testid="username"/> aka locator('form').filter({ hasText: '*password should have at' }).getByTestId('username') Call log: - waiting for getByTestId('username') ❯ tests/local.spec.ts:76:25
await passwordInput.fill('hunter2')

// Click button and wait for API to finish
const responsePromise = page.waitForResponse(/\/api\/auth\/signup/)
await submitButton.click()
const response = await responsePromise

// Expect the response to return signup data
const responseBody = await response.json() // Parse response
playwrightExpect(responseBody).toBeDefined() // Ensure data is returned
})
})
9 changes: 5 additions & 4 deletions src/runtime/composables/local/useAuth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { type Ref, readonly } from 'vue'

import type { CommonUseAuthReturn, GetSessionOptions, SecondarySignInOptions, SignInFunc, SignOutFunc, SignUpOptions } from '../../types'
import { jsonPointerGet, objectFromJsonPointer, useTypedBackendConfig } from '../../helpers'
import { _fetch } from '../../utils/fetch'
Expand Down Expand Up @@ -160,17 +159,19 @@ async function getSession(getSessionOptions?: GetSessionOptions): Promise<Sessio
return data.value
}

async function signUp(credentials: Credentials, signInOptions?: SecondarySignInOptions, signUpOptions?: SignUpOptions) {
async function signUp<T>(credentials: Credentials, signInOptions?: SecondarySignInOptions, signUpOptions?: SignUpOptions): Promise<T> {
const nuxt = useNuxtApp()

const { path, method } = useTypedBackendConfig(useRuntimeConfig(), 'local').endpoints.signUp
await _fetch(nuxt, path, {

// Holds result from fetch to be returned if signUpOptions?.preventLoginFlow is true
const result = await _fetch<T>(nuxt, path, {
method,
body: credentials
})

if (signUpOptions?.preventLoginFlow) {
return
return result
}

return signIn(credentials, signInOptions)
Expand Down
Loading