diff --git a/composables/useAuthFetch.js b/composables/useAuthFetch.js index 9e95d4a..72e7d40 100644 --- a/composables/useAuthFetch.js +++ b/composables/useAuthFetch.js @@ -1,18 +1,6 @@ export const useAuthFetch = async (url, options = {}) => { const { $auth } = useNuxtApp() - const token = await $auth.retrieveToken() - - if (!token) { - return await $auth.redirectToLogin() - } - - options = Object.assign(options, { - headers: { - 'Authorization': 'Bearer ' + token - } - }) - const res = await useFetch(url, options) if (res.error.value && res.error.value.status === 401) { await $auth.logout() diff --git a/docker-compose.yml b/docker-compose.yml index 8083a24..15320e3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,12 @@ services: - DJANGO_SUPERUSER_USERNAME=admin # default superuser name - DJANGO_SUPERUSER_PASSWORD=password # default superuser password - DJANGO_SUPERUSER_EMAIL=admin@example.com # default superuser email + # If you want to use the email verification function, you need to configure the following parameters +# - EMAIL_HOST=SMTP server address +# - EMAIL_PORT=SMTP server port +# - EMAIL_HOST_USER= +# - EMAIL_HOST_PASSWORD= +# - EMAIL_USE_TLS=True ports: - '8000:8000' networks: diff --git a/nuxt.config.ts b/nuxt.config.ts index a7ca469..9e747d1 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -53,7 +53,8 @@ export default defineNuxtConfig({ devProxy: { "/api": { target: "http://localhost:8000/api", - prependPath: true + prependPath: true, + changeOrigin: true, } } diff --git a/pages/account/onboarding.vue b/pages/account/onboarding.vue new file mode 100644 index 0000000..cf34e02 --- /dev/null +++ b/pages/account/onboarding.vue @@ -0,0 +1,81 @@ + + + + + + + + + + Verify your email + + We've sent a verification email to {{ $auth.user.email }}. + Please check your inbox and click the link to verify your email address. + + {{ errorMsg }} + + {{ resent ? 'Resent' : 'Resend email'}} + + + + + + + + + + \ No newline at end of file diff --git a/pages/login.vue b/pages/account/signin.vue similarity index 53% rename from pages/login.vue rename to pages/account/signin.vue index 7676a39..1eb2368 100644 --- a/pages/login.vue +++ b/pages/account/signin.vue @@ -1,6 +1,5 @@ @@ -8,13 +7,14 @@ - Sign in + Sign in - {{ errorMsg }} - - - Submit - + + + {{ errorMsg }} + + + Create account + + Submit + + @@ -70,17 +82,30 @@ const errorMsg = ref(null) const signInForm = ref(null) const valid = ref(true) const submitting = ref(false) +const route = useRoute() + const submit = async () => { errorMsg.value = null const { valid } = await signInForm.value.validate() if (valid) { submitting.value = true - const error = await $auth.login(formData.value.username, formData.value.password) - submitting.value = false - if (!error) { - return await $auth.callback() + const { data, error } = await useFetch('/api/account/login/', { + method: 'POST', + body: JSON.stringify(formData.value) + }) + if (error.value) { + if (error.value.status === 400) { + if (error.value.data.non_field_errors) { + errorMsg.value = error.value.data.non_field_errors[0] + } + } else { + errorMsg.value = 'Something went wrong. Please try again.' + } + } else { + $auth.setUser(data.value.user) + navigateTo(route.query.callback || '/') } - errorMsg.value = error + submitting.value = false } } diff --git a/pages/account/signup.vue b/pages/account/signup.vue new file mode 100644 index 0000000..b01f4f8 --- /dev/null +++ b/pages/account/signup.vue @@ -0,0 +1,172 @@ + + + + + + + + + Create your account + + + + + + + + + + + + + {{ errorMsg }} + + + Sign in instead + + Submit + + + + + + + + + \ No newline at end of file diff --git a/pages/account/verify-email.vue b/pages/account/verify-email.vue new file mode 100644 index 0000000..d5e79e6 --- /dev/null +++ b/pages/account/verify-email.vue @@ -0,0 +1,100 @@ + + + + + + + Verifying your email + + + + + + + Your email has been verified. + + + You can now sign in to your account. + + + Sign in + + + + + There was an error verifying your email. + + + Resend email + + + + + + + \ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue index 1972cf4..f6dd049 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -18,16 +18,14 @@ const abortFetch = () => { fetchingResponse.value = false } const fetchReply = async (message, parentMessageId) => { - const token = await $auth.retrieveToken() ctrl = new AbortController() try { - await fetchEventSource('/api/conversation', { + await fetchEventSource('/api/conversation/', { signal: ctrl.signal, method: 'POST', headers: { 'accept': 'application/json', 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ model: currentModel.value, diff --git a/plugins/auth.js b/plugins/auth.js index 0761434..0288d03 100644 --- a/plugins/auth.js +++ b/plugins/auth.js @@ -1,40 +1,20 @@ const AUTH_ROUTE = { home: '/', - login: '/login' -} - -const COOKIE_OPTIONS = { - prefix: '_Secure-auth', - path: '/', - tokenName: 'access-token', - refreshTokenName: 'refresh-token', + login: '/account/signin', } const ENDPOINTS = { login: { - url: '/api/auth/signin' - }, - refresh: { - url: '/api/auth/token/refresh' + url: '/api/account/login/' }, user: { - url: '/api/auth/session' + url: '/api/account/user/' } } export default defineNuxtPlugin(() => { - const tokenKey = COOKIE_OPTIONS.prefix + '.' + COOKIE_OPTIONS.tokenName - const refreshTokenKey = COOKIE_OPTIONS.prefix + '.' + COOKIE_OPTIONS.refreshTokenName - const tokenOptions = { - maxAge: 60 * 5, - } - const refreshTokenOptions = { - maxAge: 60 * 60 * 24, - } - const token = useCookie(tokenKey, tokenOptions) - const refreshToken = useCookie(refreshTokenKey, refreshTokenOptions) class Auth { constructor() { @@ -42,73 +22,32 @@ export default defineNuxtPlugin(() => { this.user = useState('user') } - async login (username, password) { - const { data, error } = await useFetch(ENDPOINTS.login.url, { - method: 'POST', - body: { - username, - password - } - }) - if (!error.value) { - token.value = data.value.access - refreshToken.value = data.value.refresh - return null - } - if (error.value.status === 401) { - return error.value.data.detail - } - return 'Request failed, please try again.' - } - async logout () { this.loginIn.value = false this.user.value = null await this.redirectToLogin() } + setUser (user) { + this.user = user + this.loginIn.value = true + } + async fetchUser () { - const { data, error } = await useAuthFetch(ENDPOINTS.user.url) + const { data, error } = await useFetch(ENDPOINTS.user.url, { + // withCredentials: true + }) if (!error.value) { - this.user = data.value - this.loginIn.value = true + this.setUser(data.value) return null } return error } - async refresh () { - const { data, error } = await useFetch(ENDPOINTS.refresh.url, { - method: 'POST', - body: { - 'refresh': refreshToken.value - } - }) - if (!error.value) { - token.value = data.value.access - return data.value.access - } - return null - } - - async callback () { - return await navigateTo(AUTH_ROUTE.home) - } - - async redirectToLogin () { - return await navigateTo(AUTH_ROUTE.login) - } - - async retrieveToken () { - const token = useCookie(tokenKey, tokenOptions) - const refreshToken = useCookie(refreshTokenKey, refreshTokenOptions) - if (!refreshToken.value) { - return null - } - if (!token.value) { - return await this.refresh() - } - return token.value + async redirectToLogin (callback) { + return await navigateTo( + AUTH_ROUTE.login + '?callback=' + encodeURIComponent(callback || AUTH_ROUTE.home) + ) } } @@ -117,13 +56,9 @@ export default defineNuxtPlugin(() => { addRouteMiddleware('auth', async (to, from) => { if (!auth.loginIn.value) { - const token = await auth.retrieveToken() - if (!token) { - return await auth.redirectToLogin() - } const error = await auth.fetchUser() if (error) { - return await auth.redirectToLogin() + return await auth.redirectToLogin(to.fullPath) } } }) diff --git a/utils/helper.js b/utils/helper.js index 6607d02..b3c0b6c 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -9,7 +9,7 @@ export const getDefaultConversionData = () => { } export const getConversions = async () => { - const { data, error } = await useAuthFetch('/api/chat/conversations') + const { data, error } = await useAuthFetch('/api/chat/conversations/') if (!error.value) { return data.value } @@ -33,7 +33,7 @@ export const openConversationMessages = async (currentConversation) => { } export const genTitle = async (conversationId) => { - const { data, error } = await useAuthFetch('/api/gen_title', { + const { data, error } = await useAuthFetch('/api/gen_title/', { method: 'POST', body: { conversationId: conversationId
+ We've sent a verification email to {{ $auth.user.email }}. + Please check your inbox and click the link to verify your email address. +
{{ errorMsg }}
+ You can now sign in to your account. +