200 lines
7.6 KiB
TypeScript
200 lines
7.6 KiB
TypeScript
// file: ~/server/api/auth/[...].ts
|
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
import { NuxtAuthHandler } from '#auth'
|
|
import CredentialsProvider from 'next-auth/providers/credentials';
|
|
import GithubProvider from 'next-auth/providers/github'
|
|
import GoogleProvider from 'next-auth/providers/google';
|
|
|
|
export default NuxtAuthHandler({
|
|
// @ts-ignore
|
|
secret: useRuntimeConfig().authSecret,
|
|
|
|
providers: [
|
|
// @ts-expect-error
|
|
GithubProvider.default({
|
|
clientId: useRuntimeConfig().githubClientId,
|
|
clientSecret: useRuntimeConfig().githubClientSecret
|
|
}),
|
|
// @ts-expect-error
|
|
GoogleProvider.default({
|
|
clientId: useRuntimeConfig().googleClientId,
|
|
clientSecret: useRuntimeConfig().googleClientSecret
|
|
}),
|
|
// @ts-expect-error
|
|
CredentialsProvider.default({
|
|
name: 'credentials',
|
|
credentials: {
|
|
email: { label: 'email', type: 'email' },
|
|
password: { type: 'password', label: 'password' }
|
|
},
|
|
async authorize(credentials: { email: string; password: string } | undefined) {
|
|
if (!credentials) return null;
|
|
|
|
const config = useRuntimeConfig();
|
|
const apiUrl = config.public.BASE_API_URL || 'http://127.0.0.1:8000';
|
|
|
|
try {
|
|
const tokenResponse = await fetch(`${apiUrl}/api/v1/auth/jwt/create/`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email: credentials.email,
|
|
password: credentials.password
|
|
})
|
|
});
|
|
|
|
if (!tokenResponse.ok) {
|
|
return null;
|
|
}
|
|
|
|
const tokenData = await tokenResponse.json();
|
|
const { access, refresh } = tokenData;
|
|
|
|
const userResponse = await fetch(`${apiUrl}/api/v1/auth/users/me/`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${access}`
|
|
}
|
|
});
|
|
|
|
if (!userResponse.ok) {
|
|
return null;
|
|
}
|
|
|
|
const userData = await userResponse.json();
|
|
|
|
// Return a user object with tokens and user data
|
|
return {
|
|
...userData,
|
|
accessToken: access,
|
|
refreshToken: refresh
|
|
};
|
|
} catch (error) {
|
|
console.error('Authorize error:', error);
|
|
return null;
|
|
}
|
|
}
|
|
})
|
|
],
|
|
callbacks: {
|
|
// @ts-ignore
|
|
jwt: async ({ token, user, account }) => {
|
|
// The `user` object is from the `authorize` callback.
|
|
if (user) {
|
|
// @ts-ignore
|
|
token.id = user.id;
|
|
// @ts-ignore
|
|
token.email = user.email;
|
|
// @ts-ignore
|
|
token.name = user.name;
|
|
// @ts-ignore
|
|
token.role = user.role;
|
|
// @ts-ignore
|
|
token.image = user.image;
|
|
// @ts-ignore
|
|
token.is_active = user.is_active;
|
|
// Persist the tokens in the JWT
|
|
// @ts-ignore
|
|
token.accessToken = user.accessToken;
|
|
// @ts-ignore
|
|
token.refreshToken = user.refreshToken;
|
|
}
|
|
|
|
// Social login: exchange provider access_token for backend JWT tokens
|
|
// This runs only on initial sign-in where `account` is present.
|
|
if (
|
|
account &&
|
|
(account.provider === 'google' || account.provider === 'github') &&
|
|
// @ts-ignore
|
|
account.access_token
|
|
) {
|
|
try {
|
|
const config = useRuntimeConfig();
|
|
const apiUrl = config.public.BASE_API_URL || 'http://127.0.0.1:8000';
|
|
|
|
const endpoint =
|
|
account.provider === 'google'
|
|
? `${apiUrl}/api/v1/auth/social/google-oauth2/`
|
|
: `${apiUrl}/api/v1/auth/social/github/`;
|
|
|
|
// @ts-ignore
|
|
const accessToken = account.access_token;
|
|
const response = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ access_token: accessToken }),
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
// Expected shape (per docs): { access, refresh, user }
|
|
// Persist backend JWTs on token so client can call protected APIs.
|
|
// @ts-ignore
|
|
token.accessToken = data.access;
|
|
// @ts-ignore
|
|
token.refreshToken = data.refresh;
|
|
|
|
if (data.user) {
|
|
// @ts-ignore
|
|
token.id = data.user.id ?? token.id;
|
|
// @ts-ignore
|
|
token.email = data.user.email ?? token.email;
|
|
// @ts-ignore
|
|
token.name =
|
|
(data.user.name ??
|
|
[data.user.first_name, data.user.last_name].filter(Boolean).join(' ')) ||
|
|
token.name;
|
|
// @ts-ignore
|
|
token.image = data.user.image ?? token.image;
|
|
// @ts-ignore
|
|
token.role = data.user.role ?? token.role;
|
|
// @ts-ignore
|
|
token.is_active = data.user.is_active ?? token.is_active;
|
|
}
|
|
} else {
|
|
const text = await response.text().catch(() => '');
|
|
console.error('Social token exchange failed:', response.status, text);
|
|
}
|
|
} catch (e) {
|
|
console.error('Social token exchange error:', e);
|
|
}
|
|
}
|
|
|
|
return token;
|
|
},
|
|
// @ts-ignore
|
|
session: async ({ session, token }) => {
|
|
if (token && session.user) {
|
|
// @ts-ignore
|
|
session.user.id = token.id;
|
|
// @ts-ignore
|
|
session.user.name = token.name;
|
|
// @ts-ignore
|
|
session.user.role = token.role;
|
|
// @ts-ignore
|
|
session.user.email = token.email;
|
|
// @ts-ignore
|
|
session.user.image = token.image;
|
|
// @ts-ignore
|
|
session.user.is_active = token.is_active;
|
|
}
|
|
|
|
// Expose backend JWTs to the client session (optional but useful)
|
|
// @ts-ignore
|
|
session.accessToken = token.accessToken;
|
|
// @ts-ignore
|
|
session.refreshToken = token.refreshToken;
|
|
|
|
return session;
|
|
},
|
|
},
|
|
// Disable verbose NextAuth logs to avoid DEBUG_ENABLED warning
|
|
// @ts-ignore
|
|
debug: false,
|
|
pages: {
|
|
signIn: '/auth/login'
|
|
},
|
|
session: {
|
|
strategy: 'jwt'
|
|
}
|
|
})
|