Init
This commit is contained in:
Generated
+35
@@ -583,6 +583,28 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-extra"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
@@ -935,6 +957,17 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@@ -2346,6 +2379,7 @@ dependencies = [
|
||||
"argon2",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"bitflags",
|
||||
"chrono",
|
||||
"config",
|
||||
@@ -2360,6 +2394,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tower",
|
||||
|
||||
@@ -13,6 +13,7 @@ members = [".", "migration", "event_bus"]
|
||||
[dependencies]
|
||||
tokio = { version = "1.52.3", features = ["full"] }
|
||||
axum = { version = "0.8", features = ["ws"] }
|
||||
axum-extra = { version = "0.12.6", features = ["cookie"] }
|
||||
config = "0.15.24"
|
||||
sea-orm = { version = "2.0.0-rc.41", features = ["sqlx-sqlite", "sqlx-postgres", "sqlx-mysql", "runtime-tokio", "with-chrono", "with-uuid", "with-json", "schema-sync"] }
|
||||
migration = { path = "migration" }
|
||||
@@ -39,3 +40,4 @@ async-trait = "0.1.89"
|
||||
anyhow = "1.0.102"
|
||||
futures-util = "0.3"
|
||||
form_urlencoded = "1.2.2"
|
||||
time = "0.3.47"
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-main>
|
||||
<router-view />
|
||||
</v-main>
|
||||
</v-app>
|
||||
<router-view/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
//
|
||||
//
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,8 @@ import {useAuthStore} from "@/stores/auth";
|
||||
export function useApi() {
|
||||
const appStore = useAppStore()
|
||||
const authStore = useAuthStore()
|
||||
const baseUrl = `${appStore.baseurl}/api`
|
||||
// const baseUrl = `${appStore.baseurl}/api`
|
||||
const baseUrl = '/api'
|
||||
|
||||
const request = async (endpoint: string, options: RequestInit = {}) => {
|
||||
const headers = new Headers(options.headers)
|
||||
@@ -13,9 +14,9 @@ export function useApi() {
|
||||
headers.set('Content-Type', 'application/json')
|
||||
}
|
||||
|
||||
if (authStore.token) {
|
||||
headers.set('Authorization', `Bearer ${authStore.token}`)
|
||||
}
|
||||
// if (authStore.token) {
|
||||
// headers.set('Authorization', `Bearer ${authStore.token}`)
|
||||
// }
|
||||
|
||||
const config = {
|
||||
...options,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app class="admin-layout">
|
||||
<v-app-bar color="error" dark>
|
||||
<v-app-bar-title>Console d'Administration</v-app-bar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn prepend-icon="mdi-arrow-left" to="/">Retour à l'App</v-btn>
|
||||
</v-app-bar>
|
||||
|
||||
<v-navigation-drawer permanent>
|
||||
<v-list>
|
||||
<v-list-item subtitle="Contrôle" title="Admin Panel"></v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item prepend-icon="mdi-view-dashboard" title="Dashboard" to="/admin"></v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main>
|
||||
<v-container fluid>
|
||||
<!-- Les pages d'administration s'injecteront ici -->
|
||||
<router-view/>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app>
|
||||
<!--Top bar-->
|
||||
<v-system-bar>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-icon>mdi-square</v-icon>
|
||||
|
||||
<v-icon>mdi-circle</v-icon>
|
||||
|
||||
<v-icon>mdi-triangle</v-icon>
|
||||
</v-system-bar>
|
||||
|
||||
<v-navigation-drawer
|
||||
color="grey-lighten-3"
|
||||
rail
|
||||
>
|
||||
<v-avatar
|
||||
class="d-block text-center mx-auto mt-4"
|
||||
color="grey-darken-1"
|
||||
size="36"
|
||||
></v-avatar>
|
||||
|
||||
<v-divider class="mx-3 my-5"></v-divider>
|
||||
|
||||
<v-avatar
|
||||
v-for="n in 6"
|
||||
:key="n"
|
||||
class="d-block text-center mx-auto mb-9"
|
||||
color="grey-lighten-1"
|
||||
size="28"
|
||||
></v-avatar>
|
||||
</v-navigation-drawer>
|
||||
<v-navigation-drawer width="244">
|
||||
<v-sheet
|
||||
color="grey-lighten-5"
|
||||
height="128"
|
||||
width="100%"
|
||||
></v-sheet>
|
||||
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="n in 5"
|
||||
:key="n"
|
||||
:title="`Item ${ n }`"
|
||||
link
|
||||
></v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main>
|
||||
<!-- Les pages de l'application s'injecteront ici -->
|
||||
<router-view/>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app class="auth-layout">
|
||||
<v-main>
|
||||
<!-- On injecte directement les pages qui gèrent déjà leur propre centrage et v-card -->
|
||||
<router-view/>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -137,7 +137,7 @@ async function handleRegister() {
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
<div class="text-center mt-2">
|
||||
<router-link to="/login">Déjà un compte ? Connectez-vous</router-link>
|
||||
<router-link to="/auth/login">Déjà un compte ? Connectez-vous</router-link>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
@@ -93,7 +93,7 @@ async function handleLogin() {
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
<div class="text-center mt-4">
|
||||
<router-link to="/join">Pas encore de compte ? Rejoindre</router-link>
|
||||
<router-link to="/auth/join">Pas encore de compte ? Rejoindre</router-link>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
@@ -6,31 +6,47 @@
|
||||
|
||||
// Composables
|
||||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import Index from '@/pages/index.vue'
|
||||
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import AuthLayout from "@/layouts/AuthLayout.vue";
|
||||
import AppLayout from "@/layouts/AppLayout.vue";
|
||||
import AdminLayout from "@/layouts/AdminLayout.vue";
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
// ----------------- GROUPE AUTHENTIFICATION -----------------
|
||||
{
|
||||
path: '/login',
|
||||
path: '/auth',
|
||||
component: AuthLayout,
|
||||
children: [
|
||||
{
|
||||
path: 'login',
|
||||
name: 'login',
|
||||
component: () => import('@/pages/login.vue'),
|
||||
component: () => import('@/pages/auth/login.vue'),
|
||||
},
|
||||
{
|
||||
path: '/join',
|
||||
path: 'join',
|
||||
name: 'join',
|
||||
component: () => import('@/pages/join.vue'),
|
||||
component: () => import('@/pages/auth/join.vue'),
|
||||
},
|
||||
// Vous pouvez ajouter ici 'reset-password', etc.
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------- GROUPE APPLICATION GÉNÉRALE -----------------
|
||||
{
|
||||
path: '/',
|
||||
component: Index,
|
||||
component: AppLayout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'home',
|
||||
component: () => import('@/pages/index.vue'),
|
||||
},
|
||||
{
|
||||
// Cette regex capture soit un UUID, soit le mot "default"
|
||||
path: '/server/:id(default|[0-9a-fA-F-]{36})',
|
||||
path: 'server/:id(default|[0-9a-fA-F-]{36})',
|
||||
name: 'server-dashboard',
|
||||
component: () => import('@/pages/server/index.vue'),
|
||||
props: true,
|
||||
@@ -43,11 +59,22 @@ const router = createRouter({
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------- GROUPE ADMINISTRATION -----------------
|
||||
{
|
||||
path: '/admin',
|
||||
component: AdminLayout,
|
||||
meta: {requiresAdmin: true},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'admin-dashboard',
|
||||
component: () => import('@/pages/admin/dashboard.vue'),
|
||||
meta: {requiresAdmin: true}
|
||||
},
|
||||
// Ajoutez d'autres pages admin ici
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -64,7 +91,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
if (authRequired && !authStore.isAuthenticated) {
|
||||
// Non connecté -> Login
|
||||
next('/login')
|
||||
next('/auth/login')
|
||||
} else if (to.name === 'login' && authStore.isAuthenticated) {
|
||||
// Déjà connecté -> Accueil
|
||||
next('/')
|
||||
|
||||
@@ -34,8 +34,11 @@ export const useAppStore = defineStore('app', {
|
||||
// On s'abonne à l'événement de connexion de la gateway
|
||||
window.addEventListener('gateway:connected', loadAppData)
|
||||
|
||||
// On connecte la gateway (qui déclenchera 'gateway:connected')
|
||||
await gatewayStore.connect()
|
||||
|
||||
// Initialisation de l'authentification (qui lancera la connexion à la gateway)
|
||||
await authStore.initialize()
|
||||
// await authStore.initialize()
|
||||
|
||||
// Sécurité : si la gateway s'est déjà connectée entre-temps
|
||||
if (gatewayStore.status === 'connected') {
|
||||
|
||||
+67
-25
@@ -1,6 +1,5 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {useApi} from "@/composables/useApi";
|
||||
import {useGatewayStore} from "@/stores/gateway.ts";
|
||||
|
||||
export interface User {
|
||||
id: string
|
||||
@@ -13,57 +12,100 @@ export interface User {
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
token: localStorage.getItem('token') || null as string | null,
|
||||
// Plus de token stocké dans le localStorage !
|
||||
user: null as User | null,
|
||||
isInitialized: false,
|
||||
// Nous ne gardons ce token en mémoire que pour la connexion au WebSocket
|
||||
// gatewayToken: null as string | null,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
isAuthenticated: (state) => !!state.token && !!state.user,
|
||||
isAuthenticated: (state) => !!state.user,
|
||||
isAdmin: (state) => state.user?.is_superuser || false,
|
||||
currentUser: (state) => state.user,
|
||||
},
|
||||
|
||||
actions: {
|
||||
async initialize() {
|
||||
if (!this.token) {
|
||||
this.isInitialized = true
|
||||
return
|
||||
}
|
||||
|
||||
const api = useApi()
|
||||
try {
|
||||
const response = await api.get('/auth/me')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
this.user = data.user
|
||||
|
||||
// Initialisation du gateway
|
||||
const gatewayStore = useGatewayStore()
|
||||
await gatewayStore.connect()
|
||||
} else {
|
||||
this.logout()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Auth initialization failed", e)
|
||||
this.logout()
|
||||
// this.logout()
|
||||
} finally {
|
||||
this.isInitialized = true
|
||||
}
|
||||
},
|
||||
|
||||
setToken(token: string) {
|
||||
this.token = token
|
||||
localStorage.setItem('token', token)
|
||||
// On déclenche la récupération des infos utilisateur immédiatement
|
||||
return this.initialize()
|
||||
},
|
||||
|
||||
logout() {
|
||||
this.token = null
|
||||
const api = useApi()
|
||||
this.user = null
|
||||
localStorage.removeItem('token')
|
||||
// On ne redirige pas ici pour laisser le router ou le composant décider
|
||||
api.post('/auth/logout')
|
||||
// Note : si vous implémentez une route /auth/logout côté Axum,
|
||||
// elle devra nettoyer le cookie en renvoyant un Set-Cookie expiré.
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
////// Version with token + local storage
|
||||
// export const useAuthStore = defineStore('auth', {
|
||||
// state: () => ({
|
||||
// token: localStorage.getItem('token') || null as string | null,
|
||||
// user: null as User | null,
|
||||
// isInitialized: false,
|
||||
// }),
|
||||
//
|
||||
// getters: {
|
||||
// isAuthenticated: (state) => !!state.token && !!state.user,
|
||||
// isAdmin: (state) => state.user?.is_superuser || false,
|
||||
// currentUser: (state) => state.user,
|
||||
// },
|
||||
//
|
||||
// actions: {
|
||||
// async initialize() {
|
||||
// if (!this.token) {
|
||||
// this.isInitialized = true
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// const api = useApi()
|
||||
// try {
|
||||
// const response = await api.get('/auth/me')
|
||||
// if (response.ok) {
|
||||
// const data = await response.json()
|
||||
// this.user = data.user
|
||||
//
|
||||
// // Initialisation du gateway
|
||||
// const gatewayStore = useGatewayStore()
|
||||
// await gatewayStore.connect()
|
||||
// } else {
|
||||
// this.logout()
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error("Auth initialization failed", e)
|
||||
// this.logout()
|
||||
// } finally {
|
||||
// this.isInitialized = true
|
||||
// }
|
||||
// },
|
||||
//
|
||||
// setToken(token: string) {
|
||||
// this.token = token
|
||||
// localStorage.setItem('token', token)
|
||||
// // On déclenche la récupération des infos utilisateur immédiatement
|
||||
// return this.initialize()
|
||||
// },
|
||||
//
|
||||
// logout() {
|
||||
// this.token = null
|
||||
// this.user = null
|
||||
// localStorage.removeItem('token')
|
||||
// // On ne redirige pas ici pour laisser le router ou le composant décider
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
@@ -8,7 +8,7 @@ export const useCategoryStore = defineStore("category", {
|
||||
actions: {
|
||||
async fetchCategories() {
|
||||
let api = useApi();
|
||||
let response = await api.get("/api/categories");
|
||||
let response = await api.get("/categories");
|
||||
this.categories = await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const useChannelStore = defineStore('channel', {
|
||||
actions: {
|
||||
async fetchChannels() {
|
||||
let api = useApi();
|
||||
let response = await api.get("/api/channels");
|
||||
let response = await api.get("/channels");
|
||||
this.channels = await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {useAppStore} from "@/stores/app.ts";
|
||||
import {useAuthStore} from "@/stores/auth.ts";
|
||||
|
||||
type GatewayStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
|
||||
|
||||
@@ -18,20 +17,21 @@ export const useGatewayStore = defineStore('gateway', {
|
||||
}
|
||||
|
||||
const appStore = useAppStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// const authStore = useAuthStore()
|
||||
|
||||
this.status = 'connecting'
|
||||
const token = authStore.token
|
||||
if (!token) {
|
||||
this.status = 'error'
|
||||
return
|
||||
}
|
||||
// version token
|
||||
// const token = authStore.token
|
||||
// if (!token) {
|
||||
// this.status = 'error'
|
||||
// return
|
||||
// }
|
||||
|
||||
const apiUri = appStore.baseurl ? new URL(appStore.baseurl) : new URL(window.location.href)
|
||||
const wsProtocol = apiUri.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
|
||||
const wsUrl = `${wsProtocol}//${apiUri.host}/ws/gateway?token=${encodeURIComponent(token)}`
|
||||
// version token
|
||||
// const wsUrl = `${wsProtocol}//${apiUri.host}/ws/gateway?token=${encodeURIComponent(token)}`
|
||||
const wsUrl = `${wsProtocol}//${apiUri.host}/ws/gateway`
|
||||
const socket = new WebSocket(wsUrl)
|
||||
|
||||
socket.onopen = () => {
|
||||
|
||||
@@ -8,7 +8,7 @@ export const useServerStore = defineStore("server", {
|
||||
actions: {
|
||||
async fetchServers() {
|
||||
let api = useApi();
|
||||
const response = await api.get("/api/servers");
|
||||
const response = await api.get("/servers");
|
||||
this.servers = await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import {fileURLToPath, URL} from 'node:url'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import Fonts from 'unplugin-fonts/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
|
||||
import {defineConfig} from 'vite'
|
||||
import Vuetify, {transformAssetUrls} from 'vite-plugin-vuetify'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
Vue({
|
||||
template: { transformAssetUrls },
|
||||
template: {transformAssetUrls},
|
||||
}),
|
||||
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
|
||||
Vuetify({
|
||||
@@ -29,7 +29,7 @@ export default defineConfig({
|
||||
},
|
||||
}),
|
||||
],
|
||||
define: { 'process.env': {} },
|
||||
define: {'process.env': {}},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('src', import.meta.url)),
|
||||
@@ -46,5 +46,11 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -125,6 +125,16 @@ impl MigrationTrait for Migration {
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("default_channel_permissions"))
|
||||
.big_integer()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("default_voice_permissions"))
|
||||
.big_integer()
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("name")).string().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
|
||||
@@ -36,13 +36,11 @@ pub fn create_jwt(
|
||||
}
|
||||
|
||||
pub fn verify_jwt(token: &str, secret: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
|
||||
println!("Verifying token: {}", token);
|
||||
let validation = Validation::default();
|
||||
let token_data = decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(secret.as_ref()),
|
||||
&validation,
|
||||
)?;
|
||||
println!("Token data: {:?}", token_data);
|
||||
Ok(token_data.claims)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use axum::{
|
||||
middleware::Next,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tracing::{info, Instrument};
|
||||
@@ -103,6 +104,11 @@ pub async fn auth_middleware(
|
||||
None
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
// Grâce à CookieJar, on extrait proprement le cookie "token" ou "jwt"
|
||||
let jar = CookieJar::from_headers(req.headers());
|
||||
jar.get("token").map(|cookie| cookie.value().to_string())
|
||||
})
|
||||
.or_else(|| {
|
||||
req.uri().query().and_then(|q| {
|
||||
form_urlencoded::parse(q.as_bytes())
|
||||
|
||||
@@ -32,7 +32,8 @@ pub struct Model {
|
||||
pub name: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
pub default_permissions: Option<u64>,
|
||||
pub default_channel_permissions: Option<i64>,
|
||||
pub default_voice_permissions: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -6,11 +6,13 @@ use crate::http::error::HTTPError;
|
||||
use crate::routes::user::mapper::user_model_to_user_response;
|
||||
use axum::extract::State;
|
||||
use axum::Json;
|
||||
use axum_extra::extract::cookie::{Cookie, SameSite};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use sea_orm::ActiveModelBehavior;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/auth/login",
|
||||
post,
|
||||
path = "/auth/bearer-login",
|
||||
request_body = LoginRequest,
|
||||
responses(
|
||||
(status = 200, description = "Login successful", body = LoginResponse),
|
||||
@@ -18,7 +20,7 @@ use sea_orm::ActiveModelBehavior;
|
||||
),
|
||||
tag = "Auth"
|
||||
)]
|
||||
pub async fn login_user_pw(
|
||||
pub async fn login_bearer(
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<LoginRequest>,
|
||||
) -> Result<Json<LoginResponse>, HTTPError> {
|
||||
@@ -41,6 +43,69 @@ pub async fn login_user_pw(
|
||||
Ok(Json(LoginResponse { token }))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/auth/login",
|
||||
request_body = LoginRequest,
|
||||
responses(
|
||||
(status = 200, description = "Login successful with cookie set", body = LoginResponse),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
tag = "Auth"
|
||||
)]
|
||||
pub async fn login_cookie(
|
||||
State(state): State<AppState>,
|
||||
jar: CookieJar,
|
||||
Json(payload): Json<LoginRequest>,
|
||||
) -> Result<(CookieJar, Json<LoginResponse>), HTTPError> {
|
||||
let user = state
|
||||
.repositories
|
||||
.user
|
||||
.check_password(&payload.username, &payload.password)
|
||||
.await
|
||||
.map_err(|_| HTTPError::Unauthorized)?;
|
||||
|
||||
let token = create_jwt(
|
||||
user.id,
|
||||
&user.username,
|
||||
user.is_superuser,
|
||||
&state.config.jwt.secret,
|
||||
state.config.jwt.duration,
|
||||
)
|
||||
.map_err(|_| HTTPError::InternalServerError("Failed to create JWT token".to_string()))?;
|
||||
|
||||
// Création du cookie sécurisé contenant le token JWT
|
||||
let cookie = Cookie::build(("token", token.clone()))
|
||||
.path("/")
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Lax)
|
||||
.secure(false) // Mettez à true si vous forcez le HTTPS en production
|
||||
.build();
|
||||
|
||||
let updated_jar = jar.add(cookie);
|
||||
|
||||
Ok((updated_jar, Json(LoginResponse { token })))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/auth/logout",
|
||||
responses(
|
||||
(status = 200, description = "Logout successful")
|
||||
),
|
||||
tag = "Auth"
|
||||
)]
|
||||
pub async fn logout_cookie(jar: CookieJar) -> Result<CookieJar, HTTPError> {
|
||||
// On crée un cookie expiré en lui donnant une durée négative de 1 seconde (ou Duration::ZERO)
|
||||
let cookie = Cookie::build(("token", ""))
|
||||
.path("/")
|
||||
.max_age(time::Duration::seconds(-1))
|
||||
.build();
|
||||
|
||||
let updated_jar = jar.add(cookie);
|
||||
Ok(updated_jar)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/auth/me",
|
||||
|
||||
@@ -5,6 +5,7 @@ use axum::Router;
|
||||
|
||||
pub fn router() -> OxRouter {
|
||||
Router::new()
|
||||
.route("/auth/login", post(handlers::login_user_pw))
|
||||
.route("/auth/login", post(handlers::login_cookie))
|
||||
.route("/auth/bearer-login", post(handlers::login_bearer))
|
||||
.route("/auth/me", get(handlers::me))
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ pub struct CreateChannelRequest {
|
||||
pub channel_type: ChannelType,
|
||||
#[schema(example = "général")]
|
||||
pub name: Option<String>,
|
||||
pub default_permissions: Option<u64>,
|
||||
pub default_channel_permissions: Option<u64>,
|
||||
pub default_voice_permissions: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
@@ -23,7 +24,8 @@ pub struct UpdateChannelRequest {
|
||||
pub position: i32,
|
||||
pub channel_type: ChannelType,
|
||||
pub name: Option<String>,
|
||||
pub default_permissions: Option<u64>,
|
||||
pub default_channel_permissions: Option<u64>,
|
||||
pub default_voice_permissions: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
@@ -36,5 +38,6 @@ pub struct ChannelResponse {
|
||||
pub name: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub default_permissions: Option<u64>,
|
||||
pub default_channel_permissions: Option<u64>,
|
||||
pub default_voice_permissions: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ pub fn channel_model_to_channel_response(model: channel::Model) -> ChannelRespon
|
||||
name: model.name,
|
||||
created_at: model.created_at,
|
||||
updated_at: model.updated_at,
|
||||
default_permissions: model.default_permissions,
|
||||
default_channel_permissions: model.default_channel_permissions.map(|p| p as u64),
|
||||
default_voice_permissions: model.default_voice_permissions.map(|p| p as u64),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +26,8 @@ pub fn create_request_to_am(req: CreateChannelRequest) -> channel::ActiveModel {
|
||||
position: Set(req.position),
|
||||
channel_type: Set(req.channel_type),
|
||||
name: Set(req.name),
|
||||
default_permissions: Set(req.default_permissions),
|
||||
default_channel_permissions: Set(req.default_channel_permissions.map(|p| p as i64)),
|
||||
default_voice_permissions: Set(req.default_voice_permissions.map(|p| p as i64)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -38,7 +40,8 @@ pub fn update_request_to_am(id: Uuid, req: UpdateChannelRequest) -> channel::Act
|
||||
position: Set(req.position),
|
||||
channel_type: Set(req.channel_type),
|
||||
name: Set(req.name),
|
||||
default_permissions: Set(req.default_permissions),
|
||||
default_channel_permissions: Set(req.default_channel_permissions.map(|p| p as i64)),
|
||||
default_voice_permissions: Set(req.default_voice_permissions.map(|p| p as i64)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::models::user::Model as User;
|
||||
use crate::routes::gateway::GatewayClient;
|
||||
use axum::{
|
||||
extract::{
|
||||
ws::{Message, WebSocket, WebSocketUpgrade}, Query,
|
||||
ws::{Message, WebSocket, WebSocketUpgrade},
|
||||
State,
|
||||
},
|
||||
response::IntoResponse,
|
||||
@@ -19,7 +19,6 @@ pub struct WsQuery {
|
||||
}
|
||||
|
||||
pub async fn ws_handler(
|
||||
Query(query): Query<WsQuery>,
|
||||
ws: WebSocketUpgrade,
|
||||
State(state): State<AppState>,
|
||||
CurrentUser(user): CurrentUser,
|
||||
|
||||
@@ -6,7 +6,9 @@ use utoipa::{Modify, OpenApi};
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
auth::handlers::login_user_pw,
|
||||
auth::handlers::login_bearer,
|
||||
auth::handlers::login_cookie,
|
||||
auth::handlers::logout_cookie,
|
||||
auth::handlers::me,
|
||||
user::handlers::get_all,
|
||||
user::handlers::get_by_id,
|
||||
|
||||
Reference in New Issue
Block a user