diff --git a/api/source/api_modules/auth.d b/api/source/api_modules/auth.d
index f6b4359..f93ea65 100644
--- a/api/source/api_modules/auth.d
+++ b/api/source/api_modules/auth.d
@@ -58,6 +58,7 @@ private Optional!User getUserFromBasicAuth(ref HttpRequestContext ctx, Connectio
import std.string : startsWith;
import std.digest.sha;
import std.algorithm : countUntil;
+ import std.conv : to;
string headerStr = ctx.request.headers.getFirst("Authorization").orElse("");
if (headerStr.length == 0 || !startsWith(headerStr, "Basic ")) {
@@ -83,6 +84,18 @@ private Optional!User getUserFromBasicAuth(ref HttpRequestContext ctx, Connectio
) {
return Optional!User.empty;
}
+ // Check if an admin user is requesting to view the application as a given user.
+ if (optUser.value.isAdmin && ctx.request.headers.contains("X-Admin-As-User")) {
+ string userAsIdHeader = ctx.request.headers.getFirst("X-Admin-As-User").orElse("");
+ ulong userId = userAsIdHeader.to!ulong;
+ infoF!"Admin user %s is viewing the application as user %d."(optUser.value.username, userId);
+ return findOne(
+ conn,
+ "SELECT * FROM auth_user WHERE id = ?",
+ &User.parse,
+ userId
+ );
+ }
return optUser;
}
diff --git a/api/source/app.d b/api/source/app.d
index c233c56..ef34935 100644
--- a/api/source/app.d
+++ b/api/source/app.d
@@ -17,7 +17,7 @@ void main() {
config.defaultHeaders["Access-Control-Allow-Origin"] = "*";
config.defaultHeaders["Access-Control-Allow-Methods"] = "*";
config.defaultHeaders["Access-Control-Request-Method"] = "*";
- config.defaultHeaders["Access-Control-Allow-Headers"] = "Authorization, Content-Length, Content-Type";
+ config.defaultHeaders["Access-Control-Allow-Headers"] = "Authorization, Content-Length, Content-Type, X-Admin-As-User";
if (env == "PROD") {
config.port = 8107;
diff --git a/app/src/App.vue b/app/src/App.vue
index d4f5971..069a16c 100644
--- a/app/src/App.vue
+++ b/app/src/App.vue
@@ -15,6 +15,12 @@ async function logOut() {
authStore.logOut()
await router.replace('/')
}
+
+async function exitAdminViewAsUser() {
+ if (!authStore.state) return
+ authStore.state.adminAsUser = null
+ await router.replace('/admin-dashboard')
+}
@@ -25,7 +31,7 @@ async function logOut() {
Teacher Tools
Apps
My Account
- Admin
+ Admin
@@ -34,6 +40,12 @@ async function logOut() {
Logged in as
+
+ Viewing as
+
+
diff --git a/app/src/api/base.ts b/app/src/api/base.ts
index e1d7a62..e394af1 100644
--- a/app/src/api/base.ts
+++ b/app/src/api/base.ts
@@ -112,9 +112,13 @@ export abstract class APIClient {
protected getAuthHeaders(): HeadersInit {
if (this.authStore !== null && this.authStore.state) {
- return {
+ const headers: HeadersInit = {
Authorization: 'Basic ' + this.authStore.getBasicAuth(),
}
+ if (this.authStore.state.adminAsUser !== null) {
+ headers['X-Admin-As-User'] = '' + this.authStore.state.adminAsUser.id
+ }
+ return headers
}
return {}
}
diff --git a/app/src/components/AdminAnnouncementCreator.vue b/app/src/components/AdminAnnouncementCreator.vue
new file mode 100644
index 0000000..c9ad2e8
--- /dev/null
+++ b/app/src/components/AdminAnnouncementCreator.vue
@@ -0,0 +1,45 @@
+
+
+
+
diff --git a/app/src/components/AdminUsersTable.vue b/app/src/components/AdminUsersTable.vue
new file mode 100644
index 0000000..9b6d59c
--- /dev/null
+++ b/app/src/components/AdminUsersTable.vue
@@ -0,0 +1,140 @@
+
+
+
+
Users
+
+
+ Page: {{ usersPage + 1 }}
+
+
+
+
+
+
+
+ ID |
+ Username |
+ Created At |
+ Admin |
+ Locked |
+ Actions |
+
+
+
+
+ {{ user.id }} |
+ {{ user.username }} |
+ {{ new Date(user.createdAt).toLocaleString() }} |
+ {{ user.isAdmin }}
+ |
+ {{ user.isLocked
+ }} |
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ Are you sure you want to delete this user?
+
+
+
+
+
diff --git a/app/src/components/AnnouncementsBanner.vue b/app/src/components/AnnouncementsBanner.vue
index 2c5bb60..98a8246 100644
--- a/app/src/components/AnnouncementsBanner.vue
+++ b/app/src/components/AnnouncementsBanner.vue
@@ -58,7 +58,8 @@ async function deleteAnnouncement(a: Announcement) {
{{ a.message }}
-
+
diff --git a/app/src/stores/auth.ts b/app/src/stores/auth.ts
index 29a718b..35ba247 100644
--- a/app/src/stores/auth.ts
+++ b/app/src/stores/auth.ts
@@ -1,20 +1,20 @@
import type { User } from '@/api/auth'
import { defineStore } from 'pinia'
-import { computed, ref, type Ref } from 'vue'
+import { ref, type Ref } from 'vue'
export interface Authenticated {
username: string
password: string
user: User
+ adminAsUser: User | null
}
export type AuthenticationState = Authenticated | null
export const useAuthStore = defineStore('auth', () => {
const state: Ref = ref(null)
- const admin = computed(() => state.value && state.value.user.isAdmin)
function logIn(username: string, password: string, user: User) {
- state.value = { username: username, password: password, user: user }
+ state.value = { username: username, password: password, user: user, adminAsUser: null }
}
function logOut() {
state.value = null
@@ -23,5 +23,12 @@ export const useAuthStore = defineStore('auth', () => {
if (!state.value) throw new Error('User is not authenticated.')
return btoa(state.value.username + ':' + state.value.password)
}
- return { state, admin, logIn, logOut, getBasicAuth }
+ function isAdmin() {
+ return (
+ state.value &&
+ state.value.user.isAdmin &&
+ (state.value.adminAsUser === null || state.value.adminAsUser.isAdmin)
+ )
+ }
+ return { state, isAdmin, logIn, logOut, getBasicAuth }
})
diff --git a/app/src/views/AdminDashboardView.vue b/app/src/views/AdminDashboardView.vue
index db9bec9..7529ee4 100644
--- a/app/src/views/AdminDashboardView.vue
+++ b/app/src/views/AdminDashboardView.vue
@@ -1,10 +1,9 @@
@@ -84,47 +36,7 @@ async function doCreateAnnouncement() {
On this page, you'll find tools for managing users and checking audit information.
- Users
-
-
- Page: {{ usersPage }}
-
-
-
-
-
-
- ID |
- Username |
- Created At |
- Admin |
- Locked |
- Actions |
-
-
-
-
- {{ user.id }} |
- {{ user.username }} |
- {{ new Date(user.createdAt).toLocaleString() }} |
- {{ user.isAdmin }} |
- {{ user.isLocked }} |
-
-
-
-
-
-
-
- |
-
-
-
+
-
-
-
-
- Are you sure you want to delete this user?
-
-
+
diff --git a/app/src/views/MyAccountView.vue b/app/src/views/MyAccountView.vue
index 26f3c14..6e5bac0 100644
--- a/app/src/views/MyAccountView.vue
+++ b/app/src/views/MyAccountView.vue
@@ -1,32 +1,42 @@
-
+
My Account
Internal ID |
- {{ authStore.state.user.id }} |
+ {{ user.id }} |
Username |
- {{ authStore.state.user.username }} |
+ {{ user.username }} |
Created At |
- {{ new Date(authStore.state.user.createdAt).toLocaleString() }} |
+ {{ new Date(user.createdAt).toLocaleString() }} |
Account Locked |
- {{ authStore.state.user.isLocked }} |
+ {{ user.isLocked }} |
Administrator |
- {{ authStore.state.user.isAdmin }} |
+ {{ user.isAdmin }} |