Feature/Auth: last one was authentication, this one is authorization #4

Merged
homeburger merged 33 commits from feature/auth into main 2026-04-23 00:18:39 -05:00
11 changed files with 97 additions and 71 deletions
Showing only changes of commit f271ff59f8 - Show all commits

6
.env
View File

@@ -2,3 +2,9 @@
sike you thought I was like that
hehehehee (urp so full)
# TODO: should have basic public-safe environment variables here
# then secret environment variables can be added via secrets in the ci script like so:
# job: inject-seccrets $ echo API_KEY={{ secrets.API_KEY }} >> .env
# then they dont have to be inserted by the docker container ( messy)

View File

@@ -23,7 +23,23 @@ public class UsersController : ControllerBase {
[Authorize(Policy = "RequireAdmin")]
[HttpGet]
public async Task<ActionResult<List<User>>> getUsers() {
return Ok(await service_.GetAll());
List<User> rawArray = await service_.GetAll();
List<UserDto> dtoArray = new List<UserDto>();
foreach(User user in rawArray) {
// TODO: can you operator overload a cast? if so cast<UserDto>(UserDto) would go hard
// if not then just a new custom cast function that returns a dto type will do
UserDto newDto = new UserDto{
CreatedAt = user.CreatedAt,
Email = user.Email,
Id = user.Id,
UserName = user.UserName
};
dtoArray.Add(newDto);
}
return Ok(dtoArray);
}
[Authorize(Policy = "RequireAdmin")]
@@ -34,7 +50,14 @@ public class UsersController : ControllerBase {
if (user == null) return NotFound();
return Ok(user);
UserDto newDto = new UserDto{
CreatedAt = user.CreatedAt,
Email = user.Email,
Id = user.Id,
UserName = user.UserName
};
return Ok(newDto);
}
[Authorize(Policy = "RequireSuperuser")]

View File

@@ -46,3 +46,12 @@ public class LoginDto {
public string Password { get; set; } = "";
}
public class UserDto {
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; // gets compressed to a string
public string? Email { get; set; } = "";
public string Id { get; set; } = "";
public string? UserName { get; set; } = "";
};

View File

@@ -3,7 +3,7 @@
// handles user registration, user logins, tokens, password reset, etc.
import { api, authStorage } from "./axios.ts"
import type { User, RegisterDto, LoginDto } from "../models/User.ts";
import type { UserDto, RegisterDto, LoginDto } from "../models/User.ts";
const API_URL: string = "/auth";

View File

@@ -1,11 +1,11 @@
import api from "./axios.ts"
import type { User } from "../models/User.ts";
import type { UserDto } from "../models/User.ts";
const API_URL: string = "/users";
export const getUsers = () => api.get<User[]>(`${API_URL}`);
export const getUsers = () => api.get<UserDto[]>(`${API_URL}`);
export const getUser = (id: number) => api.get<User>(`${API_URL}/${id}`);
export const getUser = (id: string) => api.get<UserDto>(`${API_URL}/${id}`);
export const deleteUser = (id: number) => api.delete<User>(`${API_URL}/${id}`);
export const deleteUser = (id: string) => api.delete<UserDto>(`${API_URL}/${id}`);

View File

@@ -2,11 +2,11 @@
// models are the data objects stored in the database. models defined here must match models defined in api/models
// dtos here must match the the dtos in api/src/Modelts/Dto.cs in name (case insensitive) (types are intermediately serialized to strings)
export interface User {
id: number;
username: string;
export interface UserDto {
createdAt: string;
email: string;
password: string;
id: string;
username: string;
}
export interface RegisterDto {

View File

@@ -1,56 +0,0 @@
<!-- pages/views in vue are basically root-level full-page components -->
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useItemsStore } from "../stores/ItemsStore.ts";
import type { Item } from "../models/Item.ts";
const store = useItemsStore();
const route = useRoute();
const router = useRouter();
const item = ref<Item>({
id: 0,
name: "",
description: "",
createdAt: "",
lastEditedAt: ""
});
const id: string | undefined = route.params.id as string | undefined
onMounted(() => {
if(id) {
const existing = store.items.find(i => i.id == Number(id));
if (existing) item.value = { ...existing };
}
});
async function save(): Promise<void> {
if(id) {
await store.updateItem(Number(id), item.value);
} else {
await store.addItem(item.value);
}
router.push("/items"); // redirect
}
</script>
<template>
<div>
<h2>{{ id ? "Edit Item" : "Create Item" }}</h2> <!-- omg I love ternary operator :D -->
<form @submit.prevent="save">
<input v-model="item.name" placeholder="Name" />
<input v-model="item.description" placeholder="Name" />
<button type="submit">Save</button>
</form>
</div>
</template>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import { onMounted } from "vue"
import { useRoute, useRouter } from "vue-router";
import { useUsersStore } from "../stores/UsersStore.ts"
import * as authApi from "../api/AuthApi";
const store = useUsersStore()
const router = useRouter();
onMounted(() => {
store.fetchUsers()
})
function logout() {
authApi.logout();
router.push("/login");
}
</script>
<template>
<div>
<h1>Users</h1>
<table>
<tr v-for="user in store.users" :key="user.id">
<td>{{ user.username }}</td>
<td>
<button @click="store.removeUser(user.id)">Delete</button>
</td>
</tr>
</table>
<button @click="logout()">Logout</button>
</div>
</template>

View File

@@ -10,10 +10,16 @@
<h3>yeah im so cool rn</h3>
<h1>imagining what I could do with themes :o</h1>
<h3>TODO: if(logged in) show this stuff; else dont.</h3>
<router-link to="/items" custom v-slot="{ navigate }">
<button @click="navigate" role="link">Items</button>
</router-link>
<router-link to="/users" custom v-slot="{ navigate }">
<button @click="navigate" role="link">Users</button>
</router-link>
<router-link to="/register" custom v-slot="{ navigate }"> <!-- TODO: only if token == invalid -->
<button @click="navigate" role="link">Register</button>
</router-link>

View File

@@ -6,6 +6,7 @@ import LoginForm from "../pages/LoginForm.vue";
import RegisterForm from "../pages/RegisterForm.vue";
import ItemsList from "../pages/ItemsList.vue";
import ItemForm from "../pages/ItemForm.vue";
import UsersList from "../pages/UsersList.vue";
import index from "../pages/index.vue";
import { authStorage } from "../api/axios.ts"
@@ -18,7 +19,7 @@ const routes = [
{ path: "/items", component: ItemsList, meta: { requiresAuth: true } },
{ path: "/item/new", component: ItemForm, meta: { requiresAuth: true } },
{ path: "/item/:id", component: ItemForm, meta: { requiresAuth: true } },
{ path: "/users", component: ItemsList, meta: { requiresAuth: true } }
{ path: "/users", component: UsersList, meta: { requiresAuth: true } }
]; // I really like this
const router = createRouter({

View File

@@ -1,10 +1,10 @@
import { defineStore } from "pinia";
import type { User } from "../models/User.ts";
import type { UserDto } from "../models/User.ts";
import * as usersApi from "../api/UsersApi";
interface UserState {
users: User[];
users: UserDto[];
loading: boolean;
}
@@ -23,7 +23,7 @@ export const useUsersStore = defineStore("users", {
this.loading = false;
},
async removeUser(id: number) {
async removeUser(id: string) {
await usersApi.deleteUser(id);
this.users = this.users.filter(i => i.id !== id);
}