Skip to content

Commit eefbdf5

Browse files
committed
feat: unregistered user gets error message
1 parent 0280eca commit eefbdf5

File tree

6 files changed

+129
-24
lines changed

6 files changed

+129
-24
lines changed

playground/pages/login.vue

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
11
<script setup>
22
import { useAuthentication } from '#imports'
3+
import { ref, onMounted } from 'vue'
4+
import { useRoute } from 'vue-router'
35
46
const { login } = useAuthentication()
7+
const route = useRoute()
8+
const oauthError = ref('')
9+
10+
const errorMessages = {
11+
oauth_failed: 'Authentication failed. Please try again.',
12+
user_not_registered: 'You are not registered. Please contact an administrator or register first.',
13+
account_inactive: 'Your account is inactive. Please contact support.',
14+
oauth_not_configured: 'OAuth is not properly configured.'
15+
}
16+
17+
onMounted(() => {
18+
const errorParam = route.query.error
19+
if (errorParam && errorMessages[errorParam]) {
20+
oauthError.value = errorMessages[errorParam]
21+
}
22+
})
523
624
const handleSuccess = (user, rememberMe) => {
725
console.log('[Nuxt Users] Login successful:', user, 'Remember me:', rememberMe)
@@ -22,6 +40,11 @@ const handleSubmit = (data) => {
2240
2341
const handleGoogleLogin = () => {
2442
console.log('[Nuxt Users] Starting Google OAuth flow')
43+
oauthError.value = '' // Clear any existing errors
44+
}
45+
46+
const dismissError = () => {
47+
oauthError.value = ''
2548
}
2649
</script>
2750

@@ -36,7 +59,14 @@ const handleGoogleLogin = () => {
3659

3760
<div class="demo-section">
3861
<h2>Login Component Demo</h2>
39-
<p>Login with: rrd@webmania.cc / 123</p>
62+
</div>
63+
64+
<div v-if="oauthError" class="error-banner">
65+
<div class="error-content">
66+
<span class="error-icon">⚠️</span>
67+
<span class="error-message">{{ oauthError }}</span>
68+
<button class="error-dismiss" @click="dismissError">✕</button>
69+
</div>
4070
</div>
4171

4272
<NUsersLoginForm
@@ -152,6 +182,65 @@ nav a {
152182
margin: 0 auto;
153183
}
154184
185+
.error-banner {
186+
margin: 1rem 0;
187+
padding: 1rem;
188+
background-color: #fef2f2;
189+
border: 1px solid #fecaca;
190+
border-radius: 8px;
191+
animation: slideDown 0.3s ease-out;
192+
}
193+
194+
@keyframes slideDown {
195+
from {
196+
opacity: 0;
197+
transform: translateY(-10px);
198+
}
199+
to {
200+
opacity: 1;
201+
transform: translateY(0);
202+
}
203+
}
204+
205+
.error-content {
206+
display: flex;
207+
align-items: center;
208+
gap: 0.75rem;
209+
}
210+
211+
.error-icon {
212+
font-size: 1.25rem;
213+
flex-shrink: 0;
214+
}
215+
216+
.error-message {
217+
flex: 1;
218+
color: #991b1b;
219+
font-size: 0.875rem;
220+
font-weight: 500;
221+
}
222+
223+
.error-dismiss {
224+
background: none;
225+
border: none;
226+
color: #991b1b;
227+
cursor: pointer;
228+
font-size: 1.25rem;
229+
padding: 0;
230+
width: 1.5rem;
231+
height: 1.5rem;
232+
display: flex;
233+
align-items: center;
234+
justify-content: center;
235+
border-radius: 4px;
236+
transition: background-color 0.2s;
237+
flex-shrink: 0;
238+
}
239+
240+
.error-dismiss:hover {
241+
background-color: #fee2e2;
242+
}
243+
155244
/* Responsive design */
156245
@media (max-width: 48rem) {
157246
.demo-container {

src/cli/add-google-oauth-fields.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,19 @@ export default defineCommand({
2424
const columnNames = tableInfo.rows.map(row => row.name)
2525

2626
if (!columnNames.includes('google_id')) {
27-
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id TEXT UNIQUE`
27+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id TEXT`
2828
console.log('[Nuxt Users] Added google_id column to SQLite users table ✅')
2929
}
3030

3131
if (!columnNames.includes('profile_picture')) {
3232
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN profile_picture TEXT`
3333
console.log('[Nuxt Users] Added profile_picture column to SQLite users table ✅')
3434
}
35+
36+
if (!columnNames.includes('last_login_at')) {
37+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN last_login_at DATETIME`
38+
console.log('[Nuxt Users] Added last_login_at column to SQLite users table ✅')
39+
}
3540
}
3641

3742
if (connectorName === 'mysql') {

src/runtime/server/api/nuxt-users/auth/google/callback.get.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { defineEventHandler, sendRedirect, createError, getQuery, setCookie } from 'h3'
1+
import { defineEventHandler, sendRedirect, getQuery, setCookie } from 'h3'
22
import type { ModuleOptions } from 'nuxt-users/utils'
33
import { useRuntimeConfig } from '#imports'
4-
import {
5-
createGoogleOAuth2Client,
6-
getGoogleUserFromCode,
4+
import type { H3Event } from 'h3'
5+
import {
6+
createGoogleOAuth2Client,
7+
getGoogleUserFromCode,
78
findOrCreateGoogleUser,
8-
createAuthTokenForUser
9+
createAuthTokenForUser
910
} from '../../../../utils/google-oauth'
1011

1112
export default defineEventHandler(async (event) => {
@@ -15,7 +16,7 @@ export default defineEventHandler(async (event) => {
1516

1617
// Check if Google OAuth is configured
1718
if (!options.auth.google) {
18-
return sendRedirect(event, options.auth.google?.errorRedirect || '/login?error=oauth_not_configured')
19+
return sendRedirect(event, '/login?error=oauth_not_configured')
1920
}
2021

2122
// Handle OAuth errors
@@ -47,6 +48,13 @@ export default defineEventHandler(async (event) => {
4748
// Find or create user in database
4849
const user = await findOrCreateGoogleUser(googleUser, options)
4950

51+
// Check if user was not found and auto-registration is disabled
52+
if (!user) {
53+
console.warn(`[Nuxt Users] User not registered attempted Google OAuth login: ${googleUser.email}`)
54+
const errorRedirect = options.auth.google.errorRedirect || '/login?error=user_not_registered'
55+
return sendRedirect(event, errorRedirect)
56+
}
57+
5058
// Check if user account is active
5159
if (!user.active) {
5260
console.warn(`[Nuxt Users] Inactive user attempted Google OAuth login: ${user.email}`)
@@ -68,32 +76,23 @@ export default defineEventHandler(async (event) => {
6876

6977
setCookie(event, 'auth_token', token, cookieOptions)
7078

71-
// Update last login time
72-
const { useDb } = await import('../../../../utils/db')
73-
const db = await useDb(options)
74-
await db.sql`
75-
UPDATE {${options.tables.users}}
76-
SET last_login_at = CURRENT_TIMESTAMP
77-
WHERE id = ${user.id}
78-
`
79-
8079
console.log(`[Nuxt Users] Google OAuth login successful for user: ${user.email}`)
8180

8281
// Redirect to success page
8382
const successRedirect = options.auth.google.successRedirect || '/'
8483
return sendRedirect(event, successRedirect)
85-
86-
} catch (error) {
84+
}
85+
catch (error) {
8786
console.error('[Nuxt Users] Google OAuth callback error:', error)
8887
const errorRedirect = options.auth.google?.errorRedirect || '/login?error=oauth_failed'
8988
return sendRedirect(event, errorRedirect)
9089
}
9190
})
9291

9392
// Helper function to get request URL
94-
function getRequestURL(event: any) {
93+
const getRequestURL = (event: H3Event) => {
9594
const headers = event.node.req.headers
9695
const host = headers.host || headers[':authority']
97-
const protocol = headers['x-forwarded-proto'] || (event.node.req.socket?.encrypted ? 'https' : 'http')
96+
const protocol = headers['x-forwarded-proto'] || 'https'
9897
return new URL(`${protocol}://${host}`)
99-
}
98+
}

src/runtime/server/utils/add-google-oauth-fields.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const addGoogleOauthFields = async (options: ModuleOptions) => {
1111
if (connectorName === 'sqlite' || connectorName === 'mysql' || connectorName === 'postgresql') {
1212
// Try to add the columns, ignore errors if they already exist
1313
try {
14-
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id TEXT UNIQUE`
14+
await db.sql`ALTER TABLE {${tableName}} ADD COLUMN google_id TEXT`
1515
console.log('[Nuxt Users] Added google_id column ✅')
1616
}
1717
catch (error) {

src/runtime/server/utils/google-oauth.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ export async function generateSecurePassword(): Promise<string> {
7474
export async function findOrCreateGoogleUser(
7575
googleUser: GoogleUserInfo,
7676
moduleOptions: ModuleOptions
77-
): Promise<User> {
77+
): Promise<User | null> {
7878
const db = await useDb(moduleOptions)
7979
const usersTable = moduleOptions.tables.users
80+
const allowAutoRegistration = moduleOptions.auth.google?.allowAutoRegistration ?? false
8081

8182
// First, try to find existing user by google_id
8283
let userResult = await db.sql`
@@ -122,6 +123,11 @@ export async function findOrCreateGoogleUser(
122123
return user
123124
}
124125

126+
// Check if auto-registration is allowed
127+
if (!allowAutoRegistration) {
128+
return null
129+
}
130+
125131
// Create new user
126132
const securePassword = await generateSecurePassword()
127133

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ export interface GoogleOAuthOptions {
4444
* @default ['openid', 'profile', 'email']
4545
*/
4646
scopes?: string[]
47+
/**
48+
* Allow automatic user registration when logging in with Google for the first time
49+
* If false, only existing users with matching email can log in with Google
50+
* @default false
51+
*/
52+
allowAutoRegistration?: boolean
4753
}
4854

4955
export interface RuntimeModuleOptions {

0 commit comments

Comments
 (0)