diff --git a/frontend/src/config/http.ts b/frontend/src/config/http.ts
index 2c7a971..339f6c8 100644
--- a/frontend/src/config/http.ts
+++ b/frontend/src/config/http.ts
@@ -1,7 +1,30 @@
import axios from 'axios'
-const http = axios.create({
- baseURL: location.origin.replace('7702', '7701') + '/api/'
-})
+export const getAxiosInstance = () => {
+ const instance = axios.create({
+ baseURL: location.origin.replace('7702', '7701') + '/api/',
+ headers: {
+ Authorization: localStorage.vortexnotes_passcode
+ ? 'Bearer ' + localStorage.vortexnotes_passcode
+ : undefined
+ }
+ })
-window.$http = http
+ instance.interceptors.response.use(
+ response => response,
+ error => {
+ if (error.response) {
+ if (error.response.status === 401) {
+ localStorage.removeItem('vortexnotes_passcode')
+ localStorage.removeItem('vortexnotes_auth_scope')
+ location.href = '/auth'
+ }
+ }
+ return Promise.reject(error)
+ }
+ )
+
+ return instance
+}
+
+window.$http = getAxiosInstance()
diff --git a/frontend/src/hooks/usePermissionCheckEffect.ts b/frontend/src/hooks/usePermissionCheckEffect.ts
new file mode 100644
index 0000000..68e7980
--- /dev/null
+++ b/frontend/src/hooks/usePermissionCheckEffect.ts
@@ -0,0 +1,13 @@
+import { useEffect } from 'react'
+import { hasPermission } from '@/utils'
+import { useNavigate } from 'react-router-dom'
+
+export default function usePermissionCheckEffect(scope: string) {
+ const navigate = useNavigate()
+
+ useEffect(() => {
+ if (!hasPermission(scope)) {
+ navigate('/auth')
+ }
+ }, [navigate, scope])
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index f178957..5562f8d 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,5 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Major+Mono+Display&family=Readex+Pro:wght@200;300;400;500;600;700&display=swap');
-@import url('https://at.alicdn.com/t/c/font_4344275_jt0ar688xla.css');
+@import url('https://at.alicdn.com/t/c/font_4344275_ibfdwgo51bi.css');
@tailwind base;
@tailwind components;
diff --git a/frontend/src/screens/AuthScreen/index.tsx b/frontend/src/screens/AuthScreen/index.tsx
new file mode 100644
index 0000000..3ba0b05
--- /dev/null
+++ b/frontend/src/screens/AuthScreen/index.tsx
@@ -0,0 +1,94 @@
+import React, { useRef, useState } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { isAxiosError } from 'axios'
+import { getAxiosInstance } from '@/config/http.ts'
+import ToastPopup from '@/components/popups/ToastPopup.tsx'
+
+const AuthScreen: React.FC = () => {
+ const [passcode, setPasscode] = useState('')
+ const [loading, setLoading] = useState(false)
+ const navigate = useNavigate()
+ const inputRef = useRef
(null)
+ const [error, setError] = useState(false)
+
+ const onAuth = async () => {
+ if (loading) {
+ return
+ }
+
+ if (passcode.trim().length === 0) {
+ return inputRef.current?.focus()
+ }
+
+ setLoading(true)
+
+ try {
+ const res = await window.$http.post('auth', { passcode })
+ localStorage.vortexnotes_passcode = passcode
+ localStorage.vortexnotes_auth_scope = res.data.auth_scope
+ window.$http = getAxiosInstance()
+ navigate('/')
+ ToastPopup.show({ message: 'Authentication successful' })
+ } catch (error) {
+ setLoading(false)
+ setError(true)
+
+ if (isAxiosError(error)) {
+ if (error.response) {
+ setPasscode('')
+ return ToastPopup.show({ message: error.response.data?.message })
+ }
+ }
+
+ return ToastPopup.show({ message: 'Auth Failure' })
+ }
+ }
+
+ const inputErrorClass = error && 'border-red-500 focus:border-red-500 placeholder-red-500'
+
+ return (
+
+
+
+
+

+
+ Vortexnotes
+
+
+
+
+
+
+ )
+}
+
+export default AuthScreen
diff --git a/frontend/src/screens/EditNoteScreen/index.ts b/frontend/src/screens/EditNoteScreen/index.ts
deleted file mode 100644
index 671295f..0000000
--- a/frontend/src/screens/EditNoteScreen/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './EditNoteScreen.tsx'
diff --git a/frontend/src/screens/EditNoteScreen/EditNoteScreen.tsx b/frontend/src/screens/EditNoteScreen/index.tsx
similarity index 97%
rename from frontend/src/screens/EditNoteScreen/EditNoteScreen.tsx
rename to frontend/src/screens/EditNoteScreen/index.tsx
index 4f73af5..b7126b6 100644
--- a/frontend/src/screens/EditNoteScreen/EditNoteScreen.tsx
+++ b/frontend/src/screens/EditNoteScreen/index.tsx
@@ -6,6 +6,7 @@ import useRequest from '@/hooks/useRequest.ts'
import { NoteType } from '@/types'
import MDEditor from '@uiw/react-md-editor'
import AlertPopup from '@/components/popups/AlertPopup.tsx'
+import usePermissionCheckEffect from '@/hooks/usePermissionCheckEffect.ts'
const EditNoteScreen: React.FC = () => {
const params = useParams()
@@ -16,6 +17,7 @@ const EditNoteScreen: React.FC = () => {
const [edited, setEdited] = useState(false)
const [saving, setSaving] = useState(false)
const { data } = useRequest({ method: 'GET', url: `notes/${id}` })
+ const navigate = useNavigate()
useEffect(() => {
if (data) {
@@ -24,11 +26,11 @@ const EditNoteScreen: React.FC = () => {
}
}, [data])
+ usePermissionCheckEffect('edit')
+
const titleInputRef = useRef(null)
const contentInputRef = useRef(null)
- const navigate = useNavigate()
-
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
!saving && edited && currentLocation.pathname !== nextLocation.pathname
diff --git a/frontend/src/screens/HomeScreen/index.ts b/frontend/src/screens/HomeScreen/index.ts
deleted file mode 100644
index cce5bb9..0000000
--- a/frontend/src/screens/HomeScreen/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './HomeScreen'
diff --git a/frontend/src/screens/HomeScreen/HomeScreen.tsx b/frontend/src/screens/HomeScreen/index.tsx
similarity index 100%
rename from frontend/src/screens/HomeScreen/HomeScreen.tsx
rename to frontend/src/screens/HomeScreen/index.tsx
diff --git a/frontend/src/screens/InitScreen/index.tsx b/frontend/src/screens/InitScreen/index.tsx
new file mode 100644
index 0000000..2ed70ba
--- /dev/null
+++ b/frontend/src/screens/InitScreen/index.tsx
@@ -0,0 +1,20 @@
+import LoadingView from '@/components/LoadingView.tsx'
+import React, { useEffect } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { fetchConfig } from '@/utils/api.ts'
+
+const InitScreen: React.FC = () => {
+ const navigate = useNavigate()
+
+ useEffect(() => {
+ fetchConfig().then(() => navigate('/', { replace: true }))
+ }, [navigate])
+
+ return (
+
+
+
+ )
+}
+
+export default InitScreen
diff --git a/frontend/src/screens/NewNoteScreen/index.ts b/frontend/src/screens/NewNoteScreen/index.ts
deleted file mode 100644
index ed61af7..0000000
--- a/frontend/src/screens/NewNoteScreen/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './NewNoteScreen.tsx'
diff --git a/frontend/src/screens/NewNoteScreen/NewNoteScreen.tsx b/frontend/src/screens/NewNoteScreen/index.tsx
similarity index 97%
rename from frontend/src/screens/NewNoteScreen/NewNoteScreen.tsx
rename to frontend/src/screens/NewNoteScreen/index.tsx
index 0481038..ebd025a 100644
--- a/frontend/src/screens/NewNoteScreen/NewNoteScreen.tsx
+++ b/frontend/src/screens/NewNoteScreen/index.tsx
@@ -4,6 +4,7 @@ import { useBlocker, useNavigate } from 'react-router-dom'
import MDEditor from '@uiw/react-md-editor'
import { isAxiosError } from 'axios'
import AlertPopup from '@/components/popups/AlertPopup.tsx'
+import usePermissionCheckEffect from '@/hooks/usePermissionCheckEffect.ts'
const NewNoteScreen: React.FC = () => {
const [title, setTitle] = useState('')
@@ -20,6 +21,8 @@ const NewNoteScreen: React.FC = () => {
!saving && isEdited && currentLocation.pathname !== nextLocation.pathname
)
+ usePermissionCheckEffect('create')
+
useEffect(() => {
if (blocker.state === 'blocked') {
if (confirm('You have unsaved edits. Are you sure you want to leave?')) {
diff --git a/frontend/src/screens/NoteScreen/index.ts b/frontend/src/screens/NoteScreen/index.ts
deleted file mode 100644
index 4e1d0af..0000000
--- a/frontend/src/screens/NoteScreen/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './NoteScreen.tsx'
diff --git a/frontend/src/screens/NoteScreen/NoteScreen.tsx b/frontend/src/screens/NoteScreen/index.tsx
similarity index 70%
rename from frontend/src/screens/NoteScreen/NoteScreen.tsx
rename to frontend/src/screens/NoteScreen/index.tsx
index ee967a4..fbefcf7 100644
--- a/frontend/src/screens/NoteScreen/NoteScreen.tsx
+++ b/frontend/src/screens/NoteScreen/index.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
-import { displayName } from '@/utils'
+import { displayName, hasPermission } from '@/utils'
import { isAxiosError } from 'axios'
import { NoteType } from '@/types'
import useRequest from '@/hooks/useRequest.ts'
@@ -59,20 +59,24 @@ const NoteScreen: React.FC = () => {
<>
{displayName(note.name)}
-
-
+ {hasPermission('edit') && (
+
+ )}
+ {hasPermission('delete') && (
+
+ )}
{
const [empty, setEmpty] = useState(false)
const [loading, setLoading] = useState(false)
const navigate = useNavigate()
+
useEffect(() => {
runAsyncFunction(async () => {
setLoading(true)
diff --git a/frontend/src/utils/api.ts b/frontend/src/utils/api.ts
new file mode 100644
index 0000000..d7c4384
--- /dev/null
+++ b/frontend/src/utils/api.ts
@@ -0,0 +1,10 @@
+export async function fetchConfig() {
+ const res = await window.$http.get('config')
+ const authScopes = res.data?.auth_scope
+
+ if (authScopes) {
+ localStorage.vortexnotes_auth_scope = authScopes
+ }
+
+ return res.data
+}
diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts
index 3ac9c42..35e619c 100644
--- a/frontend/src/utils/index.ts
+++ b/frontend/src/utils/index.ts
@@ -33,3 +33,17 @@ export function onSearch(keywords: string, navigate: NavigateFunction) {
navigate(`/search?keywords=${searchWords}`)
}
+
+export const hasPermission = (scope: string) => {
+ if (!localStorage.vortexnotes_passcode) {
+ if (localStorage.vortexnotes_auth_scope?.includes(scope)) {
+ return false
+ }
+ }
+
+ return true
+}
+
+export const hasPasscode = () => {
+ return !!localStorage.vortexnotes_passcode
+}
diff --git a/vortexnotes.run.xml b/vortexnotes.run.xml
index 7011cc3..644a048 100644
--- a/vortexnotes.run.xml
+++ b/vortexnotes.run.xml
@@ -2,6 +2,10 @@
+
+
+
+