Skip to content

Commit ea73810

Browse files
Nils Barsnbars
authored andcommitted
Add auth status detection to SPA
- Backend: GET /api/v2/auth/me returns authenticated, is_admin, is_grading_assistant for the current session - Frontend: auth API client, Pinia auth store, hydrated on app mount
1 parent f876df7 commit ea73810

5 files changed

Lines changed: 88 additions & 1 deletion

File tree

spa-frontend/src/App.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
<script setup lang="ts">
22
import { onMounted, watchEffect } from 'vue';
33
import DefaultLayout from './layouts/DefaultLayout.vue';
4+
import { useAuthStore } from './stores/auth';
45
import { useNavStore } from './stores/nav';
56
import { useTheme } from './theme/useTheme';
67
8+
const auth = useAuthStore();
79
const nav = useNavStore();
810
const theme = useTheme();
911
@@ -13,7 +15,7 @@ watchEffect(() => {
1315
1416
onMounted(async () => {
1517
theme.init();
16-
await nav.hydrate();
18+
await Promise.all([auth.hydrate(), nav.hydrate()]);
1719
});
1820
</script>
1921

spa-frontend/src/api/auth.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { apiGet } from './client';
2+
3+
export interface AuthStatus {
4+
authenticated: boolean;
5+
is_admin: boolean;
6+
is_grading_assistant: boolean;
7+
}
8+
9+
export function getAuthStatus(): Promise<AuthStatus> {
10+
return apiGet<AuthStatus>('/api/v2/auth/me');
11+
}

spa-frontend/src/stores/auth.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { defineStore } from 'pinia';
2+
import { getAuthStatus } from '../api/auth';
3+
4+
interface State {
5+
authenticated: boolean;
6+
isAdmin: boolean;
7+
isGradingAssistant: boolean;
8+
hydrated: boolean;
9+
}
10+
11+
export const useAuthStore = defineStore('auth', {
12+
state: (): State => ({
13+
authenticated: false,
14+
isAdmin: false,
15+
isGradingAssistant: false,
16+
hydrated: false,
17+
}),
18+
19+
actions: {
20+
async hydrate() {
21+
try {
22+
const status = await getAuthStatus();
23+
this.authenticated = status.authenticated;
24+
this.isAdmin = status.is_admin;
25+
this.isGradingAssistant = status.is_grading_assistant;
26+
} catch {
27+
// Leave defaults — not authenticated.
28+
}
29+
this.hydrated = true;
30+
},
31+
},
32+
});

webapp/ref/frontend_api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ def spa_api_error(
4040

4141

4242
# Importing the submodules registers their routes on `refbp`.
43+
from . import auth # noqa: E402,F401
4344
from . import scoreboard # noqa: E402,F401
4445
from . import students # noqa: E402,F401

webapp/ref/frontend_api/auth.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""SPA endpoint for checking the current user's authentication status."""
2+
3+
from flask_login import current_user
4+
5+
from ref import limiter, refbp
6+
from ref.frontend_api import SPA_READ_LIMIT
7+
8+
9+
@refbp.route("/api/v2/auth/me", methods=("GET",))
10+
@limiter.limit(SPA_READ_LIMIT)
11+
def spa_api_auth_me():
12+
"""Return the authentication status of the current session.
13+
14+
Shape (authenticated):
15+
16+
{
17+
"authenticated": true,
18+
"is_admin": true,
19+
"is_grading_assistant": false
20+
}
21+
22+
Shape (not authenticated):
23+
24+
{
25+
"authenticated": false,
26+
"is_admin": false,
27+
"is_grading_assistant": false
28+
}
29+
"""
30+
if current_user.is_authenticated:
31+
return {
32+
"authenticated": True,
33+
"is_admin": current_user.is_admin,
34+
"is_grading_assistant": current_user.is_grading_assistant,
35+
}, 200
36+
37+
return {
38+
"authenticated": False,
39+
"is_admin": False,
40+
"is_grading_assistant": False,
41+
}, 200

0 commit comments

Comments
 (0)