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
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,34 @@ Make sure to set the callback URL in your OAuth app settings as `<your-domain>/a

If the redirect URL mismatch in production, this means that the module cannot guess the right redirect URL. You can set the `NUXT_OAUTH_<PROVIDER>_REDIRECT_URL` env variable to overwrite the default one.

### Password Utils

Nuxt Auth Utils provides a `hashPassword` and `verifyPassword` function to hash and verify passwords by using [scrypt](https://en.wikipedia.org/wiki/Scrypt) as it is supported in many JS runtime.

```ts
const hashedPassword = await hashPassword('user_password')

if (await verifyPassword(hashedPassword, 'user_password')) {
// Password is valid
}
```

You can configure the scrypt options in your `nuxt.config.ts`:

```ts
export default defineNuxtConfig({
modules: ['nuxt-auth-utils'],
auth: {
hash: {
scrypt: {
// See https://github.com/adonisjs/hash/blob/94637029cd526783ac0a763ec581306d98db2036/src/types.ts#L144
}
}
}
})
```


### Extend Session

We leverage hooks to let you extend the session data with your own data or log when the user clears the session.
Expand Down Expand Up @@ -414,7 +442,7 @@ npm run release
[npm-version-href]: https://npmjs.com/package/nuxt-auth-utils

[npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-auth-utils.svg?style=flat&colorA=020420&colorB=00DC82
[npm-downloads-href]: https://npmjs.com/package/nuxt-auth-utils
[npm-downloads-href]: https://npm.chart.dev/nuxt-auth-utils

[license-src]: https://img.shields.io/npm/l/nuxt-auth-utils.svg?style=flat&colorA=020420&colorB=00DC82
[license-href]: https://npmjs.com/package/nuxt-auth-utils
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "nuxt-auth-utils",
"version": "0.3.9",
"description": "Minimalist Auth module for Nuxt with SSR",
"description": "Add Authentication to Nuxt applications with secured & sealed cookies sessions.",
"repository": "Atinux/nuxt-auth-utils",
"license": "MIT",
"type": "module",
Expand Down Expand Up @@ -32,7 +32,8 @@
"test:watch": "vitest watch"
},
"dependencies": {
"@nuxt/kit": "^3.13.2",
"@adonisjs/hash": "^9.0.5",
"@nuxt/kit": "^3.13.0",
"defu": "^6.1.4",
"hookable": "^5.5.3",
"ofetch": "^1.3.4",
Expand Down
6 changes: 4 additions & 2 deletions playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const toast = useToast()
async function login() {
if (logging.value || !password.value) return
logging.value = true
await $fetch('/api/login', {
await $fetch('/api/built-in-password', {
method: 'POST',
body: {
password: password.value,
Expand Down Expand Up @@ -180,13 +180,15 @@ const providers = computed(() =>
<template
#default="{ loggedIn, clear }"
>
<AuthRegister v-if="!loggedIn" />
<AuthLogin v-if="!loggedIn" />
<UButton
v-if="!loggedIn"
size="xs"
color="gray"
@click="loginModal = true"
>
Login
Login with built-in password
</UButton>
<UDropdown :items="[providers]">
<UButton
Expand Down
1 change: 1 addition & 0 deletions playground/auth.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
declare module '#auth-utils' {
interface User {
email?: string
password?: string
spotify?: string
github?: string
Expand Down
71 changes: 71 additions & 0 deletions playground/components/AuthLogin.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script lang="ts" setup>
const isOpen = ref(false)

const { fetch } = useUserSession()
const toast = useToast()

async function login(event: SubmitEvent) {
const target = event.target as HTMLFormElement

await $fetch('/api/login', {
method: 'POST',
body: {
email: target.email.value,
password: target.password.value,
},
}).then(() => {
fetch()
isOpen.value = false

toast.add({
color: 'green',
title: 'User logged in successfully',
})
}).catch((err) => {
console.log(err)

toast.add({
color: 'red',
title: err.data?.message || err.message,
})
})
}
</script>

<template>
<UButton
size="xs"
color="gray"
@click="isOpen = true"
>
Login
</UButton>

<UDashboardModal
v-model="isOpen"
title="Login"
description="Enter your email and password"
>
<form @submit.prevent="login($event)">
<UFormGroup label="Email">
<UInput
name="email"
type="email"
/>
</UFormGroup>
<UFormGroup label="Password">
<UInput
name="password"
type="password"
/>
</UFormGroup>
<UButton
type="submit"
color="black"
class="mt-2"
>
Login
</UButton>
</form>
</UDashboardModal>
</template>
71 changes: 71 additions & 0 deletions playground/components/AuthRegister.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script lang="ts" setup>
const isOpen = ref(false)

const { fetch } = useUserSession()
const toast = useToast()

async function register(event: SubmitEvent) {
const target = event.target as HTMLFormElement

await $fetch('/api/register', {
method: 'POST',
body: {
email: target.email.value,
password: target.password.value,
},
}).then(() => {
fetch()
isOpen.value = false

toast.add({
color: 'green',
title: 'User registered successfully',
})
}).catch((err) => {
console.log(err)

toast.add({
color: 'red',
title: err.data?.message || err.message,
})
})
}
</script>

<template>
<UButton
size="xs"
color="gray"
@click="isOpen = true"
>
Register
</UButton>

<UDashboardModal
v-model="isOpen"
title="Register"
description="Enter your email and password"
>
<form @submit.prevent="register($event)">
<UFormGroup label="Email">
<UInput
name="email"
type="email"
/>
</UFormGroup>
<UFormGroup label="Password">
<UInput
name="password"
type="password"
/>
</UFormGroup>
<UButton
type="submit"
color="black"
class="mt-2"
>
Register
</UButton>
</form>
</UDashboardModal>
</template>
8 changes: 5 additions & 3 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export default defineNuxtConfig({
extends: ['@nuxt/ui-pro'],
modules: ['nuxt-auth-utils', '@nuxt/ui'],
auth: {},
ui: {
icons: ['simple-icons', 'gravity-ui'],
},
devtools: { enabled: true },
imports: {
autoImport: true,
},
nitro: {
experimental: {
database: true,
},
},
routeRules: {
'/': {
// prerender: true,
Expand Down
6 changes: 4 additions & 2 deletions playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
},
"dependencies": {
"nuxt": "^3.13.2",
"nuxt-auth-utils": "latest"
"nuxt-auth-utils": "latest",
"zod": "^3.23.8"
},
"devDependencies": {
"@iconify-json/gravity-ui": "^1.2.1"
"@iconify-json/gravity-ui": "^1.2.1",
"better-sqlite3": "^11.2.1"
}
}
18 changes: 18 additions & 0 deletions playground/server/api/built-in-password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default eventHandler(async (event) => {
const { password } = await readBody(event)

if (password !== '123456') {
throw createError({
statusCode: 401,
message: 'Wrong password',
})
}
await setUserSession(event, {
user: {
password: 'admin',
},
loggedInAt: Date.now(),
})

return {}
})
55 changes: 41 additions & 14 deletions playground/server/api/login.post.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
export default eventHandler(async (event) => {
const { password } = await readBody(event)
import { z } from 'zod'

if (password !== '123456') {
throw createError({
statusCode: 401,
message: 'Wrong password',
})
}
await setUserSession(event, {
user: {
password: 'admin',
},
loggedInAt: Date.now(),
interface DBUser {
id: number
email: string
password: string
}

export default defineLazyEventHandler(async () => {
const db = useDatabase()

await db.sql`CREATE TABLE IF NOT EXISTS users ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "email" TEXT UNIQUE NOT NULL, "password" TEXT NOT NULL)`

const invalidCredentialsError = createError({
statusCode: 401,
// This message is intentionally vague to prevent user enumeration attacks.
message: 'Invalid credentials',
})

return {}
return defineEventHandler(async (event) => {
const { email, password } = await readValidatedBody(event, z.object({
email: z.string().email(),
password: z.string().min(8),
}).parse)

const user = await db.sql<{ rows: DBUser[] }>`SELECT * FROM users WHERE email = ${email}`.then(result => result.rows[0])

if (!user) {
throw invalidCredentialsError
}

if (!(await verifyPassword(user.password, password))) {
throw invalidCredentialsError
}

await setUserSession(event, {
user: {
email,
},
loggedInAt: Date.now(),
})

return setResponseStatus(event, 201)
})
})
29 changes: 29 additions & 0 deletions playground/server/api/register.post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { z } from 'zod'

export default defineLazyEventHandler(async () => {
const db = useDatabase()

await db.sql`CREATE TABLE IF NOT EXISTS users ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "email" TEXT UNIQUE NOT NULL, "password" TEXT NOT NULL)`

return defineEventHandler(async (event) => {
const { email, password } = await readValidatedBody(event, z.object({
email: z.string().email(),
password: z.string().min(8),
}).parse)

const hashedPassword = await hashPassword(password)

await db.sql`INSERT INTO users(email, password) VALUES (${email}, ${hashedPassword})`

// In real applications, you should send a confirmation email to the user before logging them in.

await setUserSession(event, {
user: {
email,
},
loggedInAt: Date.now(),
})

return setResponseStatus(event, 201)
})
})
Loading