mirror of
https://github.com/ApfelTeeSaft/R2-Explorer.git
synced 2026-08-26 19:33:39 +00:00
Improve error messages (#77)
This commit is contained in:
@@ -2,14 +2,21 @@ import { boot } from "quasar/wrappers";
|
|||||||
import { useAuthStore } from "stores/auth-store";
|
import { useAuthStore } from "stores/auth-store";
|
||||||
import { useMainStore } from "stores/main-store";
|
import { useMainStore } from "stores/main-store";
|
||||||
|
|
||||||
export default boot(async ({ router, store }) => {
|
export default boot(async ({ app, router, store }) => {
|
||||||
// Check if theres any auth token stored, if there is, try to fetch or redirect
|
// Check if theres any auth token stored, if there is, try to fetch or redirect
|
||||||
const authStore = useAuthStore(store);
|
const authStore = useAuthStore(store);
|
||||||
const authResp = await authStore.CheckLoginInStorage(router);
|
const authResp = await authStore.CheckLoginInStorage(
|
||||||
|
router,
|
||||||
|
app.config.globalProperties.$q,
|
||||||
|
);
|
||||||
|
|
||||||
if (authResp === false) {
|
if (authResp === false) {
|
||||||
// No auth token stored, try to fetch without auth or redirect
|
// No auth token stored, try to fetch without auth or redirect
|
||||||
const mainStore = useMainStore(store);
|
const mainStore = useMainStore(store);
|
||||||
await mainStore.loadServerConfigs(router, true);
|
await mainStore.loadServerConfigs(
|
||||||
|
router,
|
||||||
|
app.config.globalProperties.$q,
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,23 +28,11 @@
|
|||||||
import LeftSidebar from "components/main/LeftSidebar.vue";
|
import LeftSidebar from "components/main/LeftSidebar.vue";
|
||||||
import RightSidebar from "components/main/RightSidebar.vue";
|
import RightSidebar from "components/main/RightSidebar.vue";
|
||||||
import TopBar from "components/main/Topbar.vue";
|
import TopBar from "components/main/Topbar.vue";
|
||||||
import { useMainStore } from "stores/main-store";
|
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MainLayout",
|
name: "MainLayout",
|
||||||
components: { TopBar, RightSidebar, LeftSidebar },
|
components: { TopBar, RightSidebar, LeftSidebar },
|
||||||
created() {
|
|
||||||
const mainStore = useMainStore();
|
|
||||||
mainStore.loadUserDisks().then((buckets) => {
|
|
||||||
if (this.$route.path === "/") {
|
|
||||||
this.$router.push({
|
|
||||||
name: "files-home",
|
|
||||||
params: { bucket: buckets[0].name },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setup() {
|
setup() {
|
||||||
const leftDrawerOpen = ref(false);
|
const leftDrawerOpen = ref(false);
|
||||||
const rightDrawerOpen = ref(false);
|
const rightDrawerOpen = ref(false);
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ import { useMainStore } from "stores/main-store";
|
|||||||
const SESSION_KEY = "r2_explorer_session_token";
|
const SESSION_KEY = "r2_explorer_session_token";
|
||||||
|
|
||||||
export const useAuthStore = defineStore("auth", {
|
export const useAuthStore = defineStore("auth", {
|
||||||
state: () => ({
|
state: () => ({}),
|
||||||
user: "",
|
|
||||||
loginMethod: "",
|
|
||||||
}),
|
|
||||||
getters: {
|
getters: {
|
||||||
isAuthenticated: (state) => !!state.user,
|
isAuthenticated: (state) => !!state.user,
|
||||||
StateUser: (state) => state.user,
|
StateUser: (state) => state.user,
|
||||||
@@ -20,7 +17,7 @@ export const useAuthStore = defineStore("auth", {
|
|||||||
|
|
||||||
api.defaults.headers.common["Authorization"] = `Basic ${token}`;
|
api.defaults.headers.common["Authorization"] = `Basic ${token}`;
|
||||||
try {
|
try {
|
||||||
await mainStore.loadServerConfigs(router);
|
await mainStore.loadServerConfigs(router, this.q);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
delete api.defaults.headers.common["Authorization"];
|
delete api.defaults.headers.common["Authorization"];
|
||||||
@@ -29,19 +26,15 @@ export const useAuthStore = defineStore("auth", {
|
|||||||
|
|
||||||
api.defaults.headers.common.Authorization = `Basic ${token}`;
|
api.defaults.headers.common.Authorization = `Basic ${token}`;
|
||||||
|
|
||||||
this.loginMethod = "basic";
|
|
||||||
this.user = form.email;
|
|
||||||
|
|
||||||
if (form.remind === true) {
|
if (form.remind === true) {
|
||||||
localStorage.setItem(SESSION_KEY, token);
|
localStorage.setItem(SESSION_KEY, token);
|
||||||
} else {
|
} else {
|
||||||
sessionStorage.setItem(SESSION_KEY, token);
|
sessionStorage.setItem(SESSION_KEY, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.replace(router.currentRoute.value.query?.next || "/");
|
|
||||||
},
|
},
|
||||||
async CheckLoginInStorage(router) {
|
async CheckLoginInStorage(router, q) {
|
||||||
let token = sessionStorage.getItem(SESSION_KEY);
|
let token = sessionStorage.getItem(SESSION_KEY);
|
||||||
|
let authed = false;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
token = localStorage.getItem(SESSION_KEY);
|
token = localStorage.getItem(SESSION_KEY);
|
||||||
}
|
}
|
||||||
@@ -53,27 +46,18 @@ export const useAuthStore = defineStore("auth", {
|
|||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
api.defaults.headers.common["Authorization"] = `Basic ${token}`;
|
api.defaults.headers.common["Authorization"] = `Basic ${token}`;
|
||||||
|
|
||||||
try {
|
authed = await mainStore.loadServerConfigs(router, q, true);
|
||||||
await mainStore.loadServerConfigs(router);
|
if (!authed) {
|
||||||
} catch (e) {
|
|
||||||
// Auth token expired
|
|
||||||
delete api.defaults.headers.common["Authorization"];
|
delete api.defaults.headers.common["Authorization"];
|
||||||
await router.replace({
|
return false;
|
||||||
name: "login",
|
|
||||||
query: { next: router.currentRoute.fullPath },
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.user = atob(token).split(":")[0];
|
return false;
|
||||||
this.loginMethod = "basic";
|
|
||||||
},
|
},
|
||||||
async LogOut(router) {
|
async LogOut(router) {
|
||||||
localStorage.removeItem(SESSION_KEY);
|
localStorage.removeItem(SESSION_KEY);
|
||||||
sessionStorage.removeItem(SESSION_KEY);
|
sessionStorage.removeItem(SESSION_KEY);
|
||||||
|
|
||||||
this.user = "";
|
|
||||||
this.loginMethod = "";
|
|
||||||
await router.replace({ name: "login" });
|
await router.replace({ name: "login" });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,13 +22,7 @@ export const useMainStore = defineStore("main", {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadUserDisks() {
|
async loadServerConfigs(router, q, handleError = false) {
|
||||||
const response = await api.get("/buckets");
|
|
||||||
|
|
||||||
this.buckets = response.data.buckets;
|
|
||||||
return response.data.buckets;
|
|
||||||
},
|
|
||||||
async loadServerConfigs(router, handleError = false) {
|
|
||||||
// This is the initial requests to server, that also checks if user needs auth
|
// This is the initial requests to server, that also checks if user needs auth
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -41,6 +35,19 @@ export const useMainStore = defineStore("main", {
|
|||||||
this.auth = response.data.auth;
|
this.auth = response.data.auth;
|
||||||
this.version = response.data.version;
|
this.version = response.data.version;
|
||||||
this.showHiddenFiles = response.data.config.showHiddenFiles;
|
this.showHiddenFiles = response.data.config.showHiddenFiles;
|
||||||
|
this.buckets = response.data.buckets;
|
||||||
|
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
if (url.searchParams.get("next")) {
|
||||||
|
await router.replace(url.searchParams.get("next"));
|
||||||
|
} else if (url.pathname === "/" || url.pathname === "/auth/login") {
|
||||||
|
await router.push({
|
||||||
|
name: "files-home",
|
||||||
|
params: { bucket: this.buckets[0].name },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
if (error.response.status === 302) {
|
if (error.response.status === 302) {
|
||||||
@@ -52,17 +59,26 @@ export const useMainStore = defineStore("main", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (handleError) {
|
if (handleError) {
|
||||||
if (error.response?.status === 401) {
|
const respText = await error.response.data;
|
||||||
|
if (respText === "Authentication error: Basic Auth required") {
|
||||||
await router.push({
|
await router.push({
|
||||||
name: "login",
|
name: "login",
|
||||||
query: { next: router.currentRoute.value.fullPath },
|
query: { next: router.currentRoute.value.fullPath },
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
q.notify({
|
||||||
|
type: "negative",
|
||||||
|
message: respText,
|
||||||
|
timeout: 10000, // we will timeout it in 10s
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/cloudflare-access": "^0.2.0",
|
"@hono/cloudflare-access": "^0.2.0",
|
||||||
"chanfana": "^2.5.1",
|
"chanfana": "^2.5.1",
|
||||||
"hono": "^4.6.14",
|
"hono": "^4.6.15",
|
||||||
"postal-mime": "^2.3.2",
|
"postal-mime": "^2.3.2",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { CreateFolder } from "./modules/buckets/createFolder";
|
|||||||
import { DeleteObject } from "./modules/buckets/deleteObject";
|
import { DeleteObject } from "./modules/buckets/deleteObject";
|
||||||
import { GetObject } from "./modules/buckets/getObject";
|
import { GetObject } from "./modules/buckets/getObject";
|
||||||
import { HeadObject } from "./modules/buckets/headObject";
|
import { HeadObject } from "./modules/buckets/headObject";
|
||||||
import { ListBuckets } from "./modules/buckets/listBuckets";
|
|
||||||
import { ListObjects } from "./modules/buckets/listObjects";
|
import { ListObjects } from "./modules/buckets/listObjects";
|
||||||
import { MoveObject } from "./modules/buckets/moveObject";
|
import { MoveObject } from "./modules/buckets/moveObject";
|
||||||
import { CompleteUpload } from "./modules/buckets/multipart/completeUpload";
|
import { CompleteUpload } from "./modules/buckets/multipart/completeUpload";
|
||||||
@@ -30,7 +29,7 @@ import type {
|
|||||||
AppContext,
|
AppContext,
|
||||||
AppEnv,
|
AppEnv,
|
||||||
AppVariables,
|
AppVariables,
|
||||||
BasicAuth,
|
BasicAuthType,
|
||||||
R2ExplorerConfig,
|
R2ExplorerConfig,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
@@ -68,23 +67,16 @@ export function R2Explorer(config?: R2ExplorerConfig) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (config.cors === true) {
|
if (config.cors === true) {
|
||||||
app.use(
|
app.use("/api/*", cors());
|
||||||
"*",
|
|
||||||
cors({
|
|
||||||
origin: "*",
|
|
||||||
allowMethods: ["*"],
|
|
||||||
credentials: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.readonly === true) {
|
if (config.readonly === true) {
|
||||||
app.use("*", readOnlyMiddleware);
|
app.use("/api/*", readOnlyMiddleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.cfAccessTeamName) {
|
if (config.cfAccessTeamName) {
|
||||||
app.use("*", cloudflareAccess(config.cfAccessTeamName));
|
app.use("/api/*", cloudflareAccess(config.cfAccessTeamName));
|
||||||
app.use("*", async (c, next) => {
|
app.use("/api/*", async (c, next) => {
|
||||||
c.set("authentication_type", "cloudflare-access");
|
c.set("authentication_type", "cloudflare-access");
|
||||||
c.set("authentication_username", c.get("accessPayload").email);
|
c.set("authentication_username", c.get("accessPayload").email);
|
||||||
await next();
|
await next();
|
||||||
@@ -97,14 +89,15 @@ export function R2Explorer(config?: R2ExplorerConfig) {
|
|||||||
scheme: "basic",
|
scheme: "basic",
|
||||||
});
|
});
|
||||||
app.use(
|
app.use(
|
||||||
"*",
|
"/api/*",
|
||||||
basicAuth({
|
basicAuth({
|
||||||
|
invalidUserMessage: "Authentication error: Basic Auth required",
|
||||||
verifyUser: (username, password, c: AppContext) => {
|
verifyUser: (username, password, c: AppContext) => {
|
||||||
const users = (
|
const users = (
|
||||||
Array.isArray(c.get("config").basicAuth)
|
Array.isArray(c.get("config").basicAuth)
|
||||||
? c.get("config").basicAuth
|
? c.get("config").basicAuth
|
||||||
: [c.get("config").basicAuth]
|
: [c.get("config").basicAuth]
|
||||||
) as BasicAuth[];
|
) as BasicAuthType[];
|
||||||
|
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
if (user.username === username && user.password === password) {
|
if (user.username === username && user.password === password) {
|
||||||
@@ -122,7 +115,6 @@ export function R2Explorer(config?: R2ExplorerConfig) {
|
|||||||
|
|
||||||
openapi.get("/api/server/config", GetInfo);
|
openapi.get("/api/server/config", GetInfo);
|
||||||
|
|
||||||
openapi.get("/api/buckets", ListBuckets);
|
|
||||||
openapi.get("/api/buckets/:bucket", ListObjects);
|
openapi.get("/api/buckets/:bucket", ListObjects);
|
||||||
openapi.post("/api/buckets/:bucket/move", MoveObject);
|
openapi.post("/api/buckets/:bucket/move", MoveObject);
|
||||||
openapi.post("/api/buckets/:bucket/folder", CreateFolder);
|
openapi.post("/api/buckets/:bucket/folder", CreateFolder);
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import { OpenAPIRoute } from "chanfana";
|
|
||||||
import type { AppContext } from "../../types";
|
|
||||||
|
|
||||||
export class ListBuckets extends OpenAPIRoute {
|
|
||||||
schema = {
|
|
||||||
operationId: "get-bucket-list",
|
|
||||||
tags: ["Buckets"],
|
|
||||||
summary: "List buckets",
|
|
||||||
};
|
|
||||||
|
|
||||||
async handle(c: AppContext) {
|
|
||||||
const buckets = [];
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(c.env)) {
|
|
||||||
if (
|
|
||||||
value.get &&
|
|
||||||
value.put &&
|
|
||||||
value.get.toString().includes("function") &&
|
|
||||||
value.put.toString().includes("function")
|
|
||||||
) {
|
|
||||||
buckets.push({ name: key });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
buckets: buckets,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,19 @@ export class GetInfo extends OpenAPIRoute {
|
|||||||
async handle(c: AppContext) {
|
async handle(c: AppContext) {
|
||||||
const { basicAuth, ...config } = c.get("config");
|
const { basicAuth, ...config } = c.get("config");
|
||||||
|
|
||||||
|
const buckets = [];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(c.env)) {
|
||||||
|
if (
|
||||||
|
value.get &&
|
||||||
|
value.put &&
|
||||||
|
value.get.toString().includes("function") &&
|
||||||
|
value.put.toString().includes("function")
|
||||||
|
) {
|
||||||
|
buckets.push({ name: key });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version: settings.version,
|
version: settings.version,
|
||||||
config: config,
|
config: config,
|
||||||
@@ -21,6 +34,7 @@ export class GetInfo extends OpenAPIRoute {
|
|||||||
username: c.get("authentication_username"),
|
username: c.get("authentication_username"),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
buckets: buckets,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import type { CloudflareAccessVariables } from "@hono/cloudflare-access";
|
import type { CloudflareAccessVariables } from "@hono/cloudflare-access";
|
||||||
import type { Context } from "hono";
|
import type { Context } from "hono";
|
||||||
|
|
||||||
export type BasicAuth = {
|
export type BasicAuthType = {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
+8
-8
@@ -80,13 +80,13 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@hono/cloudflare-access':
|
'@hono/cloudflare-access':
|
||||||
specifier: ^0.2.0
|
specifier: ^0.2.0
|
||||||
version: 0.2.0([email protected]4)
|
version: 0.2.0([email protected]5)
|
||||||
chanfana:
|
chanfana:
|
||||||
specifier: ^2.5.1
|
specifier: ^2.5.1
|
||||||
version: 2.5.1
|
version: 2.5.1
|
||||||
hono:
|
hono:
|
||||||
specifier: ^4.6.14
|
specifier: ^4.6.15
|
||||||
version: 4.6.14
|
version: 4.6.15
|
||||||
postal-mime:
|
postal-mime:
|
||||||
specifier: ^2.3.2
|
specifier: ^2.3.2
|
||||||
version: 2.3.2
|
version: 2.3.2
|
||||||
@@ -1848,8 +1848,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
[email protected]4:
|
[email protected]5:
|
||||||
resolution: {integrity: sha512-j4VkyUp2xazGJ8eCCLN1Vm/bxdvm/j5ZuU9AIjLu9vapn2M44p9L3Ktr9Vnb2RN2QtcR/wVjZVMlT5k7GJQgPw==}
|
resolution: {integrity: sha512-OiQwvAOAaI2JrABBH69z5rsctHDzFzIKJge0nYXgtzGJ0KftwLWcBXm1upJC23/omNRtnqM0gjRMbtXshPdqhQ==}
|
||||||
engines: {node: '>=16.9.0'}
|
engines: {node: '>=16.9.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
@@ -3421,9 +3421,9 @@ snapshots:
|
|||||||
|
|
||||||
'@fastify/[email protected]': {}
|
'@fastify/[email protected]': {}
|
||||||
|
|
||||||
'@hono/[email protected]([email protected]4)':
|
'@hono/[email protected]([email protected]5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
hono: 4.6.14
|
hono: 4.6.15
|
||||||
|
|
||||||
'@isaacs/[email protected]':
|
'@isaacs/[email protected]':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4748,7 +4748,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
[email protected]4: {}
|
[email protected]5: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user