Added beginning of login functionality

This commit is contained in:
2026-01-05 16:18:59 -05:00
parent 46af0fb959
commit f65d97a50a
7 changed files with 100 additions and 13 deletions

View File

@@ -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

Binary file not shown.

View File

@@ -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>

View File

@@ -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')
} }

View File

@@ -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.'

View File

@@ -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

View File

@@ -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
}