diff --git a/cmd/main.go b/cmd/main.go index 1a5a2e2..15409ba 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -10,10 +10,12 @@ import ( "github.com/joho/godotenv" ) -var ginMode string -var apiPort string -var frontPort string -var hostAddress string +var ( + ginMode string + apiPort string + frontPort string + hostAddress string +) func initEnvVariables() { if godotenv.Load() != nil { @@ -35,6 +37,7 @@ func initJWT() { func initBasicRoutes(r *gin.Engine) { r.POST("/login/", api.AuthMiddleware.LoginHandler) + r.POST("/logout/", api.AuthMiddleware.LogoutHandler) r.GET("/ping/", api.Ping) } @@ -50,6 +53,12 @@ func corsMiddleware(c *gin.Context) { c.Next() } +func jwtProxyMiddleware(c *gin.Context) { + jwtToken, _ := c.Cookie("jwt") + c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %v", jwtToken)) + c.Next() +} + func initArticlesRoutes(r *gin.Engine) { articlesRouter := r.Group("/articles") articlesRouter.Use(corsMiddleware, api.AuthMiddleware.MiddlewareFunc()) @@ -63,10 +72,11 @@ func initArticlesRoutes(r *gin.Engine) { func initGin() { r := gin.Default() - r.Use(corsMiddleware) + r.Use(jwtProxyMiddleware, corsMiddleware) if ginMode == "release" { gin.SetMode(ginMode) + api.AuthMiddleware.SecureCookie = true } initBasicRoutes(r) @@ -77,6 +87,6 @@ func initGin() { func main() { initEnvVariables() - initJWT() initGin() + initJWT() } diff --git a/internal/api/auth.go b/internal/api/auth.go index 5e44944..686319f 100644 --- a/internal/api/auth.go +++ b/internal/api/auth.go @@ -2,6 +2,7 @@ package api import ( "errors" + "net/http" "os" "time" @@ -19,13 +20,25 @@ type User struct { var UsersLocation = database.Location{Database: "gohcms", Collection: "users"} var AuthMiddleware, _ = jwt.New(&jwt.GinJWTMiddleware{ - Realm: "GohCMS", - Key: []byte(os.Getenv("APP_JWT_SECRET")), - Timeout: time.Hour, - MaxRefresh: time.Hour, - Authenticator: JWTAuthenticator, + Realm: "GohCMS", + Key: []byte(os.Getenv("APP_JWT_SECRET")), + SendCookie: true, + CookieHTTPOnly: true, + CookieSameSite: http.SameSiteStrictMode, + Timeout: time.Hour, + MaxRefresh: time.Hour, + LoginResponse: JWTLoginResponse, + Authenticator: JWTAuthenticator, }) +func JWTLoginResponse(c *gin.Context, code int, message string, expire time.Time) { + if code == http.StatusOK { + c.JSON(code, gin.H{"code": code, "message": "Successfully logged in!", "expire": expire.Format(time.RFC3339)}) + } else { + c.JSON(code, gin.H{"code": code, "message": "Something wrong has happened."}) + } +} + func JWTAuthenticator(c *gin.Context) (interface{}, error) { var user = User{} err := c.BindJSON(&user) diff --git a/web/admin-gui/src/components/Navbar.vue b/web/admin-gui/src/components/Navbar.vue index 2a353f0..8bc7072 100644 --- a/web/admin-gui/src/components/Navbar.vue +++ b/web/admin-gui/src/components/Navbar.vue @@ -2,13 +2,20 @@ import { useAuthStore } from '@/stores/AuthStore'; import { deleteCookie } from '@/utils/cookies'; import { RouterLink, useRouter } from 'vue-router' +import { baseApiUrl } from "@/utils/api" const router = useRouter() function logout() { - deleteCookie('JWTtoken') - deleteCookie('JWTexpire') + deleteCookie('jwt_expire') useAuthStore().clearAll() + + fetch(`${baseApiUrl}/logout`, { + method: 'POST', + credentials: 'include' + }) + .catch(error => console.error(error)) + router.push('/') } diff --git a/web/admin-gui/src/router/index.ts b/web/admin-gui/src/router/index.ts index e6b7a88..3c5d77a 100644 --- a/web/admin-gui/src/router/index.ts +++ b/web/admin-gui/src/router/index.ts @@ -1,7 +1,6 @@ import { createRouter, createWebHistory } from 'vue-router' import { useAuthStore } from '@/stores/AuthStore' import { nextTick } from 'vue' -import Debug from '@/views/Debug.vue' import Login from '@/views/Login.vue' import Home from '@/views/Home.vue' import Articles from '@/views/Articles.vue' @@ -19,14 +18,6 @@ const router = createRouter({ title: 'GohCMS - Connexion' } }, - { - path: '/debug', - name: 'debug', - component: Debug, - meta: { - title: 'GohCMS - Debug' - } - }, { path: '/home', name: 'home', @@ -63,13 +54,13 @@ const router = createRouter({ }) router.beforeEach(async (to, from) => { - const isTokenValid = useAuthStore().isValid() + const isJwtExpired = useAuthStore().isExpired() if (to.name === 'login') { - if (isTokenValid) return { + if (!isJwtExpired) return { name: 'home' } } else { - if (!isTokenValid) { + if (isJwtExpired) { return { name: 'login', } diff --git a/web/admin-gui/src/stores/AuthStore.ts b/web/admin-gui/src/stores/AuthStore.ts index 7bcab6f..0c3f847 100644 --- a/web/admin-gui/src/stores/AuthStore.ts +++ b/web/admin-gui/src/stores/AuthStore.ts @@ -6,23 +6,18 @@ import { useErrorsStore } from "./ErrorsStore"; export interface jwtFormat { code: number, expire: string, - token: string + message: string } export const useAuthStore = defineStore("AuthStore", () => { const expire = ref('') - const token = ref('') function clearAll(): void { expire.value = '' - token.value = '' } - function isSet(): boolean { - return token.value !== undefined && token.value !== '' - } - function isExpired(): boolean { + if (expire.value === '') return true; const tokenDate = new Date(expire.value) const currentDate = new Date() @@ -33,20 +28,14 @@ export const useAuthStore = defineStore("AuthStore", () => { return false } - function isValid(): boolean { - return isSet() && !isExpired() - } - function initStore(): void { - const JWTtoken = getCookie('JWTtoken') - const JWTexpire = getCookie('JWTexpire') - if (JWTtoken !== "" && JWTexpire !== "") { - token.value = JWTtoken + const JWTexpire = getCookie('jwt_expire') + if (JWTexpire !== "") { expire.value = JWTexpire } } initStore() - return { expire, token, isValid, clearAll } + return { expire, clearAll, isExpired} }) \ No newline at end of file diff --git a/web/admin-gui/src/utils/api.ts b/web/admin-gui/src/utils/api.ts new file mode 100644 index 0000000..811326d --- /dev/null +++ b/web/admin-gui/src/utils/api.ts @@ -0,0 +1 @@ +export const baseApiUrl = `http://${__APP_ENV__.APP_HOST_ADDRESS}:${__APP_ENV__.APP_API_PORT}` \ No newline at end of file diff --git a/web/admin-gui/src/utils/database.ts b/web/admin-gui/src/utils/database.ts index f3467bf..5d8f9da 100644 --- a/web/admin-gui/src/utils/database.ts +++ b/web/admin-gui/src/utils/database.ts @@ -1,4 +1,5 @@ import { useAuthStore } from "@/stores/AuthStore" +import { baseApiUrl } from "@/utils/api" export interface Article { titleID: string, @@ -11,12 +12,10 @@ export interface Article { online: boolean } -const baseURL = `http://${__APP_ENV__.APP_HOST_ADDRESS}:${__APP_ENV__.APP_API_PORT}` - export async function getArticles(id: string) : Promise> { - return await fetch(`${baseURL}/articles/${id}`, { + return await fetch(`${baseApiUrl}/articles/${id}`, { + credentials: 'include', method: 'GET', - headers: { "Authorization": `Bearer ${useAuthStore().token}` } }) .then(result => result.json()) .catch(error => { @@ -25,10 +24,10 @@ export async function getArticles(id: string) : Promise> { } export async function postArticle(article: Article) : Promise { - return await fetch(`${baseURL}/articles/${article.titleID}`, { + return await fetch(`${baseApiUrl}/articles/${article.titleID}`, { + credentials: 'include', method: 'POST', - headers: { "Authorization": `Bearer ${useAuthStore().token}` }, - body: JSON.stringify(article) + body: JSON.stringify(article) }) .then(result => result.json()) .catch(error => { diff --git a/web/admin-gui/src/views/Debug.vue b/web/admin-gui/src/views/Debug.vue deleted file mode 100644 index 55faf2a..0000000 --- a/web/admin-gui/src/views/Debug.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/admin-gui/src/views/Login.vue b/web/admin-gui/src/views/Login.vue index 64c925c..a7485ca 100644 --- a/web/admin-gui/src/views/Login.vue +++ b/web/admin-gui/src/views/Login.vue @@ -1,6 +1,7 @@