cobras-auth-nuxt 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +302 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +134 -0
- package/dist/module.d.ts +134 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +112 -0
- package/dist/runtime/components/CobrasDevTools.vue +89 -0
- package/dist/runtime/composables/useCobrasAuth.d.ts +0 -0
- package/dist/runtime/composables/useCobrasAuth.js +99 -0
- package/dist/runtime/composables/useCobrasDevTools.d.ts +0 -0
- package/dist/runtime/composables/useCobrasDevTools.js +69 -0
- package/dist/runtime/composables/useCobrasMode.d.ts +0 -0
- package/dist/runtime/composables/useCobrasMode.js +25 -0
- package/dist/runtime/middleware/auth.d.ts +0 -0
- package/dist/runtime/middleware/auth.js +36 -0
- package/dist/runtime/middleware/internal.d.ts +0 -0
- package/dist/runtime/middleware/internal.js +19 -0
- package/dist/runtime/plugins/auth.client.d.ts +0 -0
- package/dist/runtime/plugins/auth.client.js +122 -0
- package/dist/runtime/plugins/auth.server.d.ts +0 -0
- package/dist/runtime/plugins/auth.server.js +69 -0
- package/dist/runtime/server/api/exchange.post.d.ts +0 -0
- package/dist/runtime/server/api/exchange.post.js +49 -0
- package/dist/runtime/server/api/logout.post.d.ts +0 -0
- package/dist/runtime/server/api/logout.post.js +27 -0
- package/dist/runtime/server/api/refresh.post.d.ts +0 -0
- package/dist/runtime/server/api/refresh.post.js +24 -0
- package/dist/runtime/server/api/verify.get.d.ts +0 -0
- package/dist/runtime/server/api/verify.get.js +50 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +62 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useCobrasAuth, useCobrasDevTools } from '#imports'
|
|
3
|
+
|
|
4
|
+
const { user, isAdmin } = useCobrasAuth()
|
|
5
|
+
const { state, isAvailable, toggle, close, setFeatureFlag, getFeatureFlag, toggleDebugMode } = useCobrasDevTools()
|
|
6
|
+
|
|
7
|
+
const featureFlagInput = ref('')
|
|
8
|
+
|
|
9
|
+
function addFeatureFlag() {
|
|
10
|
+
if (featureFlagInput.value.trim()) {
|
|
11
|
+
setFeatureFlag(featureFlagInput.value.trim(), true)
|
|
12
|
+
featureFlagInput.value = ''
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<Teleport to="body">
|
|
19
|
+
<Transition name="slide">
|
|
20
|
+
<div
|
|
21
|
+
v-if="isAvailable && state.isOpen"
|
|
22
|
+
class="cobras-devtools"
|
|
23
|
+
>
|
|
24
|
+
<div class="cobras-devtools-header">
|
|
25
|
+
<span class="cobras-devtools-title">Cobras Dev Tools</span>
|
|
26
|
+
<button class="cobras-devtools-close" @click="close">×</button>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="cobras-devtools-content">
|
|
30
|
+
<!-- User Info -->
|
|
31
|
+
<div class="cobras-devtools-section">
|
|
32
|
+
<h4>User</h4>
|
|
33
|
+
<div class="cobras-devtools-info">
|
|
34
|
+
<span>{{ user?.name || 'Unknown' }}</span>
|
|
35
|
+
<span class="cobras-devtools-badge">{{ user?.role }}</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="cobras-devtools-email">{{ user?.email }}</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- Debug Mode -->
|
|
41
|
+
<div class="cobras-devtools-section">
|
|
42
|
+
<h4>Debug</h4>
|
|
43
|
+
<label class="cobras-devtools-toggle">
|
|
44
|
+
<input
|
|
45
|
+
type="checkbox"
|
|
46
|
+
:checked="state.debugMode"
|
|
47
|
+
@change="toggleDebugMode"
|
|
48
|
+
/>
|
|
49
|
+
<span>Debug Mode</span>
|
|
50
|
+
</label>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<!-- Feature Flags -->
|
|
54
|
+
<div class="cobras-devtools-section">
|
|
55
|
+
<h4>Feature Flags</h4>
|
|
56
|
+
<div class="cobras-devtools-flags">
|
|
57
|
+
<div
|
|
58
|
+
v-for="(enabled, key) in state.featureFlags"
|
|
59
|
+
:key="key"
|
|
60
|
+
class="cobras-devtools-flag"
|
|
61
|
+
>
|
|
62
|
+
<label>
|
|
63
|
+
<input
|
|
64
|
+
type="checkbox"
|
|
65
|
+
:checked="enabled"
|
|
66
|
+
@change="setFeatureFlag(String(key), !enabled)"
|
|
67
|
+
/>
|
|
68
|
+
{{ key }}
|
|
69
|
+
</label>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="cobras-devtools-add-flag">
|
|
73
|
+
<input
|
|
74
|
+
v-model="featureFlagInput"
|
|
75
|
+
placeholder="Add flag..."
|
|
76
|
+
@keyup.enter="addFeatureFlag"
|
|
77
|
+
/>
|
|
78
|
+
<button @click="addFeatureFlag">+</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</Transition>
|
|
84
|
+
</Teleport>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<style scoped>
|
|
88
|
+
.cobras-devtools{background:#1a1a2e;border:1px solid #333;border-radius:8px;bottom:20px;box-shadow:0 4px 20px rgba(0,0,0,.5);color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;position:fixed;right:20px;width:320px;z-index:99999}.cobras-devtools-header{align-items:center;background:#16213e;border-bottom:1px solid #333;border-radius:8px 8px 0 0;display:flex;justify-content:space-between;padding:12px 16px}.cobras-devtools-title{color:#fff;font-weight:600}.cobras-devtools-close{background:none;border:none;color:#888;cursor:pointer;font-size:20px;line-height:1;padding:0}.cobras-devtools-close:hover{color:#fff}.cobras-devtools-content{max-height:400px;overflow-y:auto;padding:16px}.cobras-devtools-section{margin-bottom:16px}.cobras-devtools-section:last-child{margin-bottom:0}.cobras-devtools-section h4{color:#888;font-size:11px;letter-spacing:.5px;margin:0 0 8px;text-transform:uppercase}.cobras-devtools-info{align-items:center;display:flex;gap:8px}.cobras-devtools-badge{background:#0f3460;border-radius:4px;font-size:11px;padding:2px 8px;text-transform:uppercase}.cobras-devtools-email{color:#888;font-size:12px;margin-top:4px}.cobras-devtools-toggle{align-items:center;cursor:pointer;display:flex;gap:8px}.cobras-devtools-toggle input{cursor:pointer}.cobras-devtools-flags{display:flex;flex-direction:column;gap:6px}.cobras-devtools-flag label{align-items:center;cursor:pointer;display:flex;gap:8px}.cobras-devtools-add-flag{display:flex;gap:8px;margin-top:8px}.cobras-devtools-add-flag input{background:#16213e;border:1px solid #333;border-radius:4px;color:#e0e0e0;flex:1;font-size:12px;padding:6px 10px}.cobras-devtools-add-flag button{background:#0f3460;border:none;border-radius:4px;color:#fff;cursor:pointer;padding:6px 12px}.cobras-devtools-add-flag button:hover{background:#1a4a7a}.slide-enter-active,.slide-leave-active{transition:all .2s ease}.slide-enter-from,.slide-leave-to{opacity:0;transform:translateY(20px)}
|
|
89
|
+
</style>
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useState, useRuntimeConfig, computed } from "#imports";
|
|
2
|
+
export function useCobrasAuth() {
|
|
3
|
+
const config = useRuntimeConfig();
|
|
4
|
+
const authConfig = config.public.cobrasAuth;
|
|
5
|
+
const state = useState("cobras-auth-state", () => ({
|
|
6
|
+
user: null,
|
|
7
|
+
initialized: false,
|
|
8
|
+
loading: false,
|
|
9
|
+
error: null
|
|
10
|
+
}));
|
|
11
|
+
const user = computed({
|
|
12
|
+
get: () => state.value.user,
|
|
13
|
+
set: (val) => {
|
|
14
|
+
state.value.user = val;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
const isAuthenticated = computed(() => !!state.value.user);
|
|
18
|
+
const isInternalUser = computed(() => !!state.value.user);
|
|
19
|
+
const isAdmin = computed(() => state.value.user?.role === "admin");
|
|
20
|
+
async function checkAuth() {
|
|
21
|
+
if (state.value.loading) return;
|
|
22
|
+
state.value.loading = true;
|
|
23
|
+
state.value.error = null;
|
|
24
|
+
try {
|
|
25
|
+
const response = await $fetch("/api/_cobras/verify", {
|
|
26
|
+
credentials: "include"
|
|
27
|
+
});
|
|
28
|
+
if (response.valid && response.user) {
|
|
29
|
+
state.value.user = response.user;
|
|
30
|
+
} else {
|
|
31
|
+
state.value.user = null;
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
state.value.user = null;
|
|
35
|
+
state.value.error = error.message || "Auth check failed";
|
|
36
|
+
if (authConfig.debug) {
|
|
37
|
+
console.warn("[@cobras/auth-nuxt] Auth check failed:", error);
|
|
38
|
+
}
|
|
39
|
+
} finally {
|
|
40
|
+
state.value.loading = false;
|
|
41
|
+
state.value.initialized = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function login(redirect) {
|
|
45
|
+
const currentUrl = redirect || (typeof window !== "undefined" ? window.location.href : "/");
|
|
46
|
+
const loginUrl = `${authConfig.authServiceUrl}/login?redirect=${encodeURIComponent(currentUrl)}`;
|
|
47
|
+
if (typeof window !== "undefined") {
|
|
48
|
+
window.location.href = loginUrl;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function logout() {
|
|
52
|
+
try {
|
|
53
|
+
await $fetch("/api/_cobras/logout", {
|
|
54
|
+
method: "POST",
|
|
55
|
+
credentials: "include"
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (authConfig.debug) {
|
|
59
|
+
console.warn("[@cobras/auth-nuxt] Logout error:", error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
state.value.user = null;
|
|
63
|
+
if (authConfig.mode === "internal" && typeof window !== "undefined") {
|
|
64
|
+
window.location.href = authConfig.authServiceUrl;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function verifySiteAccess() {
|
|
68
|
+
if (!state.value.user) return false;
|
|
69
|
+
if (!authConfig.siteId && !authConfig.siteDomain) return true;
|
|
70
|
+
try {
|
|
71
|
+
const response = await $fetch(
|
|
72
|
+
`${authConfig.authServiceUrl}/api/auth/check-access`,
|
|
73
|
+
{
|
|
74
|
+
method: "POST",
|
|
75
|
+
body: {
|
|
76
|
+
siteId: authConfig.siteId,
|
|
77
|
+
siteDomain: authConfig.siteDomain
|
|
78
|
+
},
|
|
79
|
+
credentials: "include"
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
return response.hasAccess;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
user,
|
|
89
|
+
state,
|
|
90
|
+
isAuthenticated,
|
|
91
|
+
isInternalUser,
|
|
92
|
+
isAdmin,
|
|
93
|
+
mode: authConfig.mode,
|
|
94
|
+
checkAuth,
|
|
95
|
+
login,
|
|
96
|
+
logout,
|
|
97
|
+
verifySiteAccess
|
|
98
|
+
};
|
|
99
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useState, useRuntimeConfig, computed } from "#imports";
|
|
2
|
+
import { useCobrasAuth } from "./useCobrasAuth.js";
|
|
3
|
+
export function useCobrasDevTools() {
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const authConfig = config.public.cobrasAuth;
|
|
6
|
+
const { isAuthenticated } = useCobrasAuth();
|
|
7
|
+
const state = useState("cobras-devtools-state", () => ({
|
|
8
|
+
isOpen: false,
|
|
9
|
+
featureFlags: {},
|
|
10
|
+
debugMode: false,
|
|
11
|
+
showMetrics: false
|
|
12
|
+
}));
|
|
13
|
+
const isAvailable = computed(() => {
|
|
14
|
+
return authConfig.enableDevTools && isAuthenticated.value;
|
|
15
|
+
});
|
|
16
|
+
function toggle() {
|
|
17
|
+
if (!isAvailable.value) return;
|
|
18
|
+
state.value.isOpen = !state.value.isOpen;
|
|
19
|
+
}
|
|
20
|
+
function open() {
|
|
21
|
+
if (!isAvailable.value) return;
|
|
22
|
+
state.value.isOpen = true;
|
|
23
|
+
}
|
|
24
|
+
function close() {
|
|
25
|
+
state.value.isOpen = false;
|
|
26
|
+
}
|
|
27
|
+
function setFeatureFlag(key, value) {
|
|
28
|
+
state.value.featureFlags[key] = value;
|
|
29
|
+
if (typeof window !== "undefined") {
|
|
30
|
+
localStorage.setItem("cobras-feature-flags", JSON.stringify(state.value.featureFlags));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getFeatureFlag(key) {
|
|
34
|
+
return state.value.featureFlags[key] ?? false;
|
|
35
|
+
}
|
|
36
|
+
function toggleDebugMode() {
|
|
37
|
+
state.value.debugMode = !state.value.debugMode;
|
|
38
|
+
if (typeof window !== "undefined") {
|
|
39
|
+
localStorage.setItem("cobras-debug-mode", String(state.value.debugMode));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function toggleMetrics() {
|
|
43
|
+
state.value.showMetrics = !state.value.showMetrics;
|
|
44
|
+
}
|
|
45
|
+
if (typeof window !== "undefined") {
|
|
46
|
+
const savedFlags = localStorage.getItem("cobras-feature-flags");
|
|
47
|
+
if (savedFlags) {
|
|
48
|
+
try {
|
|
49
|
+
state.value.featureFlags = JSON.parse(savedFlags);
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const savedDebug = localStorage.getItem("cobras-debug-mode");
|
|
54
|
+
if (savedDebug) {
|
|
55
|
+
state.value.debugMode = savedDebug === "true";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
state,
|
|
60
|
+
isAvailable,
|
|
61
|
+
toggle,
|
|
62
|
+
open,
|
|
63
|
+
close,
|
|
64
|
+
setFeatureFlag,
|
|
65
|
+
getFeatureFlag,
|
|
66
|
+
toggleDebugMode,
|
|
67
|
+
toggleMetrics
|
|
68
|
+
};
|
|
69
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useRuntimeConfig, computed } from "#imports";
|
|
2
|
+
import { useCobrasAuth } from "./useCobrasAuth.js";
|
|
3
|
+
export function useCobrasMode() {
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const authConfig = config.public.cobrasAuth;
|
|
6
|
+
const { isAuthenticated, isAdmin } = useCobrasAuth();
|
|
7
|
+
const mode = authConfig.mode;
|
|
8
|
+
const isInternalMode = mode === "internal";
|
|
9
|
+
const isPublicMode = mode === "public";
|
|
10
|
+
const showInternalFeatures = computed(() => isAuthenticated.value);
|
|
11
|
+
const showAdminFeatures = computed(() => isAdmin.value);
|
|
12
|
+
const devToolsEnabled = computed(() => {
|
|
13
|
+
if (!authConfig.enableDevTools) return false;
|
|
14
|
+
if (isPublicMode) return isAuthenticated.value;
|
|
15
|
+
return isAuthenticated.value;
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
mode,
|
|
19
|
+
isInternalMode,
|
|
20
|
+
isPublicMode,
|
|
21
|
+
showInternalFeatures: showInternalFeatures.value,
|
|
22
|
+
showAdminFeatures: showAdminFeatures.value,
|
|
23
|
+
devToolsEnabled: devToolsEnabled.value
|
|
24
|
+
};
|
|
25
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineNuxtRouteMiddleware, useRuntimeConfig, useState, navigateTo } from "#imports";
|
|
2
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
3
|
+
const config = useRuntimeConfig();
|
|
4
|
+
const authConfig = config.public.cobrasAuth;
|
|
5
|
+
const state = useState("cobras-auth-state");
|
|
6
|
+
if (to.query.code) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!state.value?.initialized) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (authConfig.mode === "public") {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const isPublicRoute = authConfig.publicRoutes.some((route) => {
|
|
16
|
+
if (route.endsWith("*")) {
|
|
17
|
+
return to.path.startsWith(route.slice(0, -1));
|
|
18
|
+
}
|
|
19
|
+
return to.path === route;
|
|
20
|
+
});
|
|
21
|
+
if (isPublicRoute) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!state.value?.user) {
|
|
25
|
+
let redirectUrl;
|
|
26
|
+
if (typeof window !== "undefined") {
|
|
27
|
+
redirectUrl = window.location.href;
|
|
28
|
+
} else {
|
|
29
|
+
redirectUrl = to.fullPath;
|
|
30
|
+
}
|
|
31
|
+
return navigateTo(
|
|
32
|
+
`${authConfig.authServiceUrl}/login?redirect_uri=${encodeURIComponent(redirectUrl)}`,
|
|
33
|
+
{ external: true }
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineNuxtRouteMiddleware, useRuntimeConfig, useState, navigateTo } from "#imports";
|
|
2
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
3
|
+
const config = useRuntimeConfig();
|
|
4
|
+
const authConfig = config.public.cobrasAuth;
|
|
5
|
+
const state = useState("cobras-auth-state");
|
|
6
|
+
if (to.query.code) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!state.value?.initialized) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!state.value?.user) {
|
|
13
|
+
const currentUrl = typeof window !== "undefined" ? window.location.href : to.fullPath;
|
|
14
|
+
return navigateTo(
|
|
15
|
+
`${authConfig.authServiceUrl}/login?redirect_uri=${encodeURIComponent(currentUrl)}`,
|
|
16
|
+
{ external: true }
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useRuntimeConfig, useState, computed, useRoute, useRouter } from "#imports";
|
|
2
|
+
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
3
|
+
const config = useRuntimeConfig();
|
|
4
|
+
const authConfig = config.public.cobrasAuth;
|
|
5
|
+
const route = useRoute();
|
|
6
|
+
const router = useRouter();
|
|
7
|
+
const state = useState("cobras-auth-state", () => ({
|
|
8
|
+
user: null,
|
|
9
|
+
initialized: false,
|
|
10
|
+
loading: false,
|
|
11
|
+
error: null
|
|
12
|
+
}));
|
|
13
|
+
const user = computed(() => state.value.user);
|
|
14
|
+
const isAuthenticated = computed(() => !!state.value.user);
|
|
15
|
+
const isInternalUser = computed(() => !!state.value.user);
|
|
16
|
+
const isAdmin = computed(() => state.value.user?.role === "admin");
|
|
17
|
+
async function exchangeCode(code2) {
|
|
18
|
+
try {
|
|
19
|
+
const response = await $fetch("/api/_cobras/exchange", {
|
|
20
|
+
method: "POST",
|
|
21
|
+
body: { code: code2 },
|
|
22
|
+
credentials: "include"
|
|
23
|
+
});
|
|
24
|
+
if (response.success && response.user) {
|
|
25
|
+
state.value.user = response.user;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (authConfig.debug) {
|
|
30
|
+
console.warn("[@cobras/auth-nuxt] Code exchange failed:", error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
async function checkAuth() {
|
|
36
|
+
if (state.value.loading) return;
|
|
37
|
+
state.value.loading = true;
|
|
38
|
+
state.value.error = null;
|
|
39
|
+
try {
|
|
40
|
+
const response = await $fetch("/api/_cobras/verify", {
|
|
41
|
+
credentials: "include"
|
|
42
|
+
});
|
|
43
|
+
if (response.valid && response.user) {
|
|
44
|
+
state.value.user = response.user;
|
|
45
|
+
} else {
|
|
46
|
+
state.value.user = null;
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
state.value.user = null;
|
|
50
|
+
if (error.statusCode !== 401 && authConfig.debug) {
|
|
51
|
+
console.warn("[@cobras/auth-nuxt] Auth check failed:", error);
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
state.value.loading = false;
|
|
55
|
+
state.value.initialized = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function login(redirect) {
|
|
59
|
+
const currentUrl = redirect || window.location.href;
|
|
60
|
+
const loginUrl = `${authConfig.authServiceUrl}/login?redirect_uri=${encodeURIComponent(currentUrl)}`;
|
|
61
|
+
window.location.href = loginUrl;
|
|
62
|
+
}
|
|
63
|
+
async function logout() {
|
|
64
|
+
try {
|
|
65
|
+
await $fetch("/api/_cobras/logout", {
|
|
66
|
+
method: "POST",
|
|
67
|
+
credentials: "include"
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (authConfig.debug) {
|
|
71
|
+
console.warn("[@cobras/auth-nuxt] Logout error:", error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
state.value.user = null;
|
|
75
|
+
if (authConfig.mode === "internal") {
|
|
76
|
+
window.location.href = authConfig.authServiceUrl;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const code = route.query.code;
|
|
80
|
+
if (code) {
|
|
81
|
+
const success = await exchangeCode(code);
|
|
82
|
+
if (success) {
|
|
83
|
+
const newQuery = { ...route.query };
|
|
84
|
+
delete newQuery.code;
|
|
85
|
+
router.replace({ query: newQuery });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await checkAuth();
|
|
89
|
+
if (authConfig.enableDevTools && authConfig.devToolsKey) {
|
|
90
|
+
const keys = authConfig.devToolsKey.toLowerCase().split("+");
|
|
91
|
+
document.addEventListener("keydown", (e) => {
|
|
92
|
+
const pressed = [
|
|
93
|
+
e.ctrlKey && "ctrl",
|
|
94
|
+
e.shiftKey && "shift",
|
|
95
|
+
e.altKey && "alt",
|
|
96
|
+
e.metaKey && "meta",
|
|
97
|
+
e.key.toLowerCase()
|
|
98
|
+
].filter(Boolean);
|
|
99
|
+
if (keys.every((k) => pressed.includes(k)) && isAuthenticated.value) {
|
|
100
|
+
const devToolsState = useState("cobras-devtools-state");
|
|
101
|
+
if (devToolsState.value) {
|
|
102
|
+
devToolsState.value.isOpen = !devToolsState.value.isOpen;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const cobrasAuth = {
|
|
108
|
+
user,
|
|
109
|
+
isAuthenticated,
|
|
110
|
+
isInternalUser,
|
|
111
|
+
isAdmin,
|
|
112
|
+
mode: authConfig.mode,
|
|
113
|
+
checkAuth,
|
|
114
|
+
login,
|
|
115
|
+
logout
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
provide: {
|
|
119
|
+
cobrasAuth
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useRuntimeConfig, useState, computed, useRequestHeaders } from "#imports";
|
|
2
|
+
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
3
|
+
const config = useRuntimeConfig();
|
|
4
|
+
const authConfig = config.public.cobrasAuth;
|
|
5
|
+
const state = useState("cobras-auth-state", () => ({
|
|
6
|
+
user: null,
|
|
7
|
+
initialized: false,
|
|
8
|
+
loading: false,
|
|
9
|
+
error: null
|
|
10
|
+
}));
|
|
11
|
+
const user = computed(() => state.value.user);
|
|
12
|
+
const isAuthenticated = computed(() => !!state.value.user);
|
|
13
|
+
const isInternalUser = computed(() => !!state.value.user);
|
|
14
|
+
const isAdmin = computed(() => state.value.user?.role === "admin");
|
|
15
|
+
async function checkAuth() {
|
|
16
|
+
if (state.value.loading) return;
|
|
17
|
+
state.value.loading = true;
|
|
18
|
+
state.value.error = null;
|
|
19
|
+
try {
|
|
20
|
+
const headers = useRequestHeaders(["cookie"]);
|
|
21
|
+
const response = await $fetch(
|
|
22
|
+
`${authConfig.authServiceUrl}/api/auth/verify`,
|
|
23
|
+
{
|
|
24
|
+
headers: {
|
|
25
|
+
cookie: headers.cookie || ""
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
if (response.valid && response.user) {
|
|
30
|
+
state.value.user = response.user;
|
|
31
|
+
} else {
|
|
32
|
+
state.value.user = null;
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
state.value.user = null;
|
|
36
|
+
if (error.statusCode !== 401 && authConfig.debug) {
|
|
37
|
+
console.warn("[@cobras/auth-nuxt] SSR auth check failed:", error.message);
|
|
38
|
+
}
|
|
39
|
+
} finally {
|
|
40
|
+
state.value.loading = false;
|
|
41
|
+
state.value.initialized = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function login(redirect) {
|
|
45
|
+
}
|
|
46
|
+
async function logout() {
|
|
47
|
+
state.value.user = null;
|
|
48
|
+
}
|
|
49
|
+
const cobrasAuth = {
|
|
50
|
+
user,
|
|
51
|
+
isAuthenticated,
|
|
52
|
+
isInternalUser,
|
|
53
|
+
isAdmin,
|
|
54
|
+
mode: authConfig.mode,
|
|
55
|
+
checkAuth,
|
|
56
|
+
login,
|
|
57
|
+
logout
|
|
58
|
+
};
|
|
59
|
+
if (authConfig.mode === "internal") {
|
|
60
|
+
await checkAuth();
|
|
61
|
+
} else {
|
|
62
|
+
state.value.initialized = true;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
provide: {
|
|
66
|
+
cobrasAuth
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineEventHandler, readBody, createError, setCookie } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const authServiceUrl = config.public.cobrasAuth.authServiceUrl;
|
|
6
|
+
const body = await readBody(event);
|
|
7
|
+
const { code } = body;
|
|
8
|
+
if (!code) {
|
|
9
|
+
throw createError({
|
|
10
|
+
statusCode: 400,
|
|
11
|
+
message: "code is required"
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const response = await $fetch(`${authServiceUrl}/api/auth/token`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
body: { code }
|
|
18
|
+
});
|
|
19
|
+
if (!response.success || !response.user) {
|
|
20
|
+
throw createError({
|
|
21
|
+
statusCode: 401,
|
|
22
|
+
message: "Invalid code"
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const session = {
|
|
26
|
+
user: response.user,
|
|
27
|
+
expires_at: response.expires_at
|
|
28
|
+
};
|
|
29
|
+
const sessionData = Buffer.from(JSON.stringify(session)).toString("base64");
|
|
30
|
+
setCookie(event, "cobras_session", sessionData, {
|
|
31
|
+
httpOnly: true,
|
|
32
|
+
secure: process.env.NODE_ENV === "production",
|
|
33
|
+
sameSite: "lax",
|
|
34
|
+
path: "/",
|
|
35
|
+
maxAge: 24 * 60 * 60
|
|
36
|
+
// 24 hours
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
user: response.user
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error("[@cobras/auth-nuxt] Code exchange failed:", error.message);
|
|
44
|
+
throw createError({
|
|
45
|
+
statusCode: error.statusCode || 500,
|
|
46
|
+
message: error.message || "Code exchange failed"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineEventHandler, getHeader, setCookie } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const authServiceUrl = config.public.cobrasAuth.authServiceUrl;
|
|
6
|
+
const cookieDomain = config.cobrasAuth?.cookieDomain;
|
|
7
|
+
const cookieHeader = getHeader(event, "cookie") || "";
|
|
8
|
+
try {
|
|
9
|
+
await $fetch(`${authServiceUrl}/api/auth/logout`, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: {
|
|
12
|
+
cookie: cookieHeader
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
const cookieOptions = {
|
|
18
|
+
httpOnly: true,
|
|
19
|
+
secure: true,
|
|
20
|
+
sameSite: "lax",
|
|
21
|
+
path: "/",
|
|
22
|
+
...cookieDomain ? { domain: cookieDomain } : {}
|
|
23
|
+
};
|
|
24
|
+
setCookie(event, "access_token", "", { ...cookieOptions, maxAge: 0 });
|
|
25
|
+
setCookie(event, "refresh_token", "", { ...cookieOptions, maxAge: 0 });
|
|
26
|
+
return { success: true };
|
|
27
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineEventHandler, getHeader, createError } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const authServiceUrl = config.public.cobrasAuth.authServiceUrl;
|
|
6
|
+
const cookieHeader = getHeader(event, "cookie") || "";
|
|
7
|
+
try {
|
|
8
|
+
const response = await $fetch(
|
|
9
|
+
`${authServiceUrl}/api/auth/refresh`,
|
|
10
|
+
{
|
|
11
|
+
method: "POST",
|
|
12
|
+
headers: {
|
|
13
|
+
cookie: cookieHeader
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
return response;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
throw createError({
|
|
20
|
+
statusCode: error.statusCode || 500,
|
|
21
|
+
message: error.message || "Token refresh failed"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
File without changes
|