Added beginning of login functionality
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import secrets
|
import secrets, jwt
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from flask import Blueprint, request, jsonify, current_app
|
from flask import Blueprint, request, jsonify, current_app
|
||||||
@@ -15,6 +15,9 @@ UserQuery = Query()
|
|||||||
mail = Mail()
|
mail = Mail()
|
||||||
TOKEN_EXPIRY_MINUTES = 60*4
|
TOKEN_EXPIRY_MINUTES = 60*4
|
||||||
|
|
||||||
|
SECRET_KEY = "your-secret-key" # Use a secure key in production
|
||||||
|
#SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||||
|
|
||||||
def send_verification_email(to_email, token):
|
def send_verification_email(to_email, token):
|
||||||
verify_url = f"{current_app.config['FRONTEND_URL']}/auth/verify?token={token}"
|
verify_url = f"{current_app.config['FRONTEND_URL']}/auth/verify?token={token}"
|
||||||
msg = Message(
|
msg = Message(
|
||||||
@@ -123,5 +126,36 @@ def login():
|
|||||||
if not user.get('verified'):
|
if not user.get('verified'):
|
||||||
return jsonify({'error': 'This account has not verified', 'code': NOT_VERIFIED}), 403
|
return jsonify({'error': 'This account has not verified', 'code': NOT_VERIFIED}), 403
|
||||||
|
|
||||||
# In production, generate and return a session token or JWT here
|
payload = {
|
||||||
return jsonify({'message': 'Login successful'}), 200
|
'email': email,
|
||||||
|
'exp': datetime.utcnow() + timedelta(hours=24*7)
|
||||||
|
}
|
||||||
|
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
|
||||||
|
|
||||||
|
resp = jsonify({'message': 'Login successful'})
|
||||||
|
resp.set_cookie('token', token, httponly=True, secure=True, samesite='Strict')
|
||||||
|
return resp, 200
|
||||||
|
|
||||||
|
@auth_api.route('/me', methods=['GET'])
|
||||||
|
def me():
|
||||||
|
token = request.cookies.get('token')
|
||||||
|
if not token:
|
||||||
|
return jsonify({'error': 'Missing token', 'code': MISSING_TOKEN}), 401
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
|
||||||
|
email = payload.get('email')
|
||||||
|
user = users_db.get(UserQuery.email == email)
|
||||||
|
if not user:
|
||||||
|
return jsonify({'error': 'User not found', 'code': USER_NOT_FOUND}), 404
|
||||||
|
return jsonify({
|
||||||
|
'email': user['email'],
|
||||||
|
'first_name': user['first_name'],
|
||||||
|
'last_name': user['last_name'],
|
||||||
|
'verified': user['verified']
|
||||||
|
}), 200
|
||||||
|
except jwt.ExpiredSignatureError:
|
||||||
|
return jsonify({'error': 'Token expired', 'code': TOKEN_EXPIRED}), 401
|
||||||
|
except jwt.InvalidTokenError:
|
||||||
|
return jsonify({'error': 'Invalid token', 'code': INVALID_TOKEN}), 401
|
||||||
|
|
||||||
|
|||||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useBackendEvents } from './common/backendEvents'
|
import { useBackendEvents } from './common/backendEvents'
|
||||||
|
import { checkAuth } from '@/stores/auth'
|
||||||
useBackendEvents('user123')
|
useBackendEvents('user123')
|
||||||
|
checkAuth()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ref, nextTick, onMounted, onUnmounted } from 'vue'
|
import { ref, nextTick, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { eventBus } from '@/common/eventBus'
|
import { eventBus } from '@/common/eventBus'
|
||||||
import { authenticateParent, isParentAuthenticated, logout } from '../stores/auth'
|
import { authenticateParent, isParentAuthenticated, logoutParent } from '../stores/auth'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const show = ref(false)
|
const show = ref(false)
|
||||||
@@ -42,7 +42,7 @@ const submit = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
logout()
|
logoutParent()
|
||||||
router.push('/child')
|
router.push('/child')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ import {
|
|||||||
ALREADY_VERIFIED,
|
ALREADY_VERIFIED,
|
||||||
} from '@/common/errorCodes'
|
} from '@/common/errorCodes'
|
||||||
import { parseErrorResponse, isEmailValid } from '@/common/api'
|
import { parseErrorResponse, isEmailValid } from '@/common/api'
|
||||||
|
import { loginUser } from '@/stores/auth' // <-- add this import
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -185,6 +186,8 @@ async function submitForm() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loginUser() // <-- set user as logged in
|
||||||
|
|
||||||
await router.push({ path: '/' }).catch(() => (window.location.href = '/'))
|
await router.push({ path: '/' }).catch(() => (window.location.href = '/'))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
loginError.value = 'Network error. Please try again.'
|
loginError.value = 'Network error. Please try again.'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { watch } from 'vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import ChildLayout from '../layout/ChildLayout.vue'
|
import ChildLayout from '../layout/ChildLayout.vue'
|
||||||
import ParentLayout from '../layout/ParentLayout.vue'
|
import ParentLayout from '../layout/ParentLayout.vue'
|
||||||
@@ -16,6 +17,7 @@ import AuthLayout from '@/layout/AuthLayout.vue'
|
|||||||
import Signup from '@/components/auth/Signup.vue'
|
import Signup from '@/components/auth/Signup.vue'
|
||||||
import AuthLanding from '@/components/auth/AuthLanding.vue'
|
import AuthLanding from '@/components/auth/AuthLanding.vue'
|
||||||
import Login from '@/components/auth/Login.vue'
|
import Login from '@/components/auth/Login.vue'
|
||||||
|
import { isUserLoggedIn, isParentAuthenticated, isAuthReady } from '../stores/auth'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@@ -147,7 +149,6 @@ const routes = [
|
|||||||
redirect: '/child',
|
redirect: '/child',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
import { isParentAuthenticated } from '../stores/auth'
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
@@ -155,13 +156,40 @@ const router = createRouter({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Auth guard
|
// Auth guard
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
if (to.meta.requiresAuth && !isParentAuthenticated.value) {
|
if (!isAuthReady.value) {
|
||||||
// Redirect to /child if trying to access /parent without auth
|
await new Promise((resolve) => {
|
||||||
next('/child')
|
const stop = watch(isAuthReady, (ready) => {
|
||||||
} else {
|
if (ready) {
|
||||||
next()
|
stop()
|
||||||
|
resolve(true)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Auth Guard:', {
|
||||||
|
to: to.fullPath,
|
||||||
|
isUserLoggedIn: isUserLoggedIn.value,
|
||||||
|
isParentAuthenticated: isParentAuthenticated.value,
|
||||||
|
})
|
||||||
|
// Always allow access to /auth routes
|
||||||
|
if (to.path.startsWith('/auth')) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not logged in, redirect to /auth
|
||||||
|
if (!isUserLoggedIn.value) {
|
||||||
|
return next('/auth')
|
||||||
|
}
|
||||||
|
|
||||||
|
// If logged in but not parent-authenticated, redirect to /child (unless already there)
|
||||||
|
if (!isParentAuthenticated.value && !to.path.startsWith('/child')) {
|
||||||
|
return next('/child')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, allow navigation
|
||||||
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -1,11 +1,31 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
export const isParentAuthenticated = ref(false)
|
export const isParentAuthenticated = ref(false)
|
||||||
|
export const isUserLoggedIn = ref(false)
|
||||||
|
export const isAuthReady = ref(false)
|
||||||
|
|
||||||
export function authenticateParent() {
|
export function authenticateParent() {
|
||||||
isParentAuthenticated.value = true
|
isParentAuthenticated.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logout() {
|
export function logoutParent() {
|
||||||
isParentAuthenticated.value = false
|
isParentAuthenticated.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loginUser() {
|
||||||
|
isUserLoggedIn.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logoutUser() {
|
||||||
|
isUserLoggedIn.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkAuth() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/me', { method: 'GET' })
|
||||||
|
isUserLoggedIn.value = res.ok
|
||||||
|
} catch {
|
||||||
|
isUserLoggedIn.value = false
|
||||||
|
}
|
||||||
|
isAuthReady.value = true
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user