mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 20:55:42 +00:00
Compare commits
4 Commits
97639ae903
...
refactor/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
673381dae6 | ||
|
|
9747ea8014 | ||
|
|
cb2521f3d9 | ||
|
|
d859f74a10 |
30
.env.example
Normal file
30
.env.example
Normal file
@@ -0,0 +1,30 @@
|
||||
PORT=3000
|
||||
ADDRESS=0.0.0.0
|
||||
SECRET=app_secret
|
||||
SECRET_FILE=app_secret_file
|
||||
APP_URL=http://localhost:3000
|
||||
USERS=your_user_password_hash
|
||||
USERS_FILE=users_file
|
||||
COOKIE_SECURE=false
|
||||
GITHUB_CLIENT_ID=github_client_id
|
||||
GITHUB_CLIENT_SECRET=github_client_secret
|
||||
GITHUB_CLIENT_SECRET_FILE=github_client_secret_file
|
||||
GOOGLE_CLIENT_ID=google_client_id
|
||||
GOOGLE_CLIENT_SECRET=google_client_secret
|
||||
GOOGLE_CLIENT_SECRET_FILE=google_client_secret_file
|
||||
TAILSCALE_CLIENT_ID=tailscale_client_id
|
||||
TAILSCALE_CLIENT_SECRET=tailscale_client_secret
|
||||
TAILSCALE_CLIENT_SECRET_FILE=tailscale__client_secret_file
|
||||
GENERIC_CLIENT_ID=generic_client_id
|
||||
GENERIC_CLIENT_SECRET=generic_client_secret
|
||||
GENERIC_CLIENT_SECRET_FILE=generic_client_secret_file
|
||||
GENERIC_SCOPES=generic_scopes
|
||||
GENERIC_AUTH_URL=generic_auth_url
|
||||
GENERIC_TOKEN_URL=generic_token_url
|
||||
GENERIC_USER_URL=generic_user_url
|
||||
DISABLE_CONTINUE=false
|
||||
OAUTH_WHITELIST=
|
||||
GENERIC_NAME=My OAuth
|
||||
SESSION_EXPIRY=7200
|
||||
LOG_LEVEL=0
|
||||
APP_TITLE=Tinyauth SSO
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -19,3 +19,9 @@ secret_oauth.txt
|
||||
|
||||
# apple stuff
|
||||
.DS_Store
|
||||
|
||||
# env
|
||||
.env
|
||||
|
||||
# tmp directory
|
||||
tmp
|
||||
@@ -8,7 +8,6 @@ Contributing is relatively easy, you just need to follow the steps carefully and
|
||||
- Golang v1.23.2 and above
|
||||
- Git
|
||||
- Docker
|
||||
- Make (not required but it will make your life easier)
|
||||
|
||||
## Cloning the repository
|
||||
|
||||
@@ -21,55 +20,33 @@ cd tinyauth
|
||||
|
||||
## Install requirements
|
||||
|
||||
To install the requirements simply run:
|
||||
Although you will not need the requirements in your machine since the development will happen in docker, I still recommend to install them because this way you will not have errors, to install the go requirements, run:
|
||||
|
||||
```sh
|
||||
make requirements
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
It will download all the node packages required by the frontend as well as all the go requirements.
|
||||
|
||||
## Developing locally
|
||||
|
||||
In order to develop the app you need to firstly compile the frontend and then the go app. To avoid running the same commands over and over again you can just run:
|
||||
You also need to download the frontend requirements, this can be done like so:
|
||||
|
||||
```sh
|
||||
make run
|
||||
cd site/
|
||||
bun install
|
||||
```
|
||||
|
||||
This is the equivalent of `go run main.go`, if you would like to build a binary run:
|
||||
## Create your `.env` file
|
||||
|
||||
```sh
|
||||
make build
|
||||
```
|
||||
In order to ocnfigure the app you need to create an environment file, this can be done by copying the `.env.example` file to `.env` and modifying the environment variables inside to suit your needs.
|
||||
|
||||
To avoid rebuilding the frontend every time you can run:
|
||||
## Developing
|
||||
|
||||
```sh
|
||||
make run-no-web
|
||||
```
|
||||
|
||||
And:
|
||||
|
||||
```sh
|
||||
make build-no-web
|
||||
```
|
||||
|
||||
For these commands to succeed you must have built the frontend at least once.
|
||||
|
||||
> [!WARNING]
|
||||
> Make sure you have set the environment variables when running outside of docker else the app will fail.
|
||||
|
||||
## Developing in docker
|
||||
|
||||
My recommended development method is docker so I can test that both my image works and that the app responds correctly to traefik. In my setup I have set these two DNS records in my DNS server:
|
||||
I have designed the development workflow to be entirely in docker, this is because it will directly work with traefik and you will not need to do any building in your host machine. The recommended development setup is to have a subdomain pointing to your machine like this:
|
||||
|
||||
```
|
||||
*.dev.example.com -> 127.0.0.1
|
||||
dev.example.com -> 127.0.0.1
|
||||
```
|
||||
|
||||
Then I can just make sure the domains are correct in the example docker compose file and do:
|
||||
Then you can just make sure the domains are correct in the example docker compose file and run:
|
||||
|
||||
```sh
|
||||
docker compose -f docker-compose.dev.yml up --build
|
||||
|
||||
22
Dockerfile.dev
Normal file
22
Dockerfile.dev
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM golang:1.23-alpine3.21
|
||||
|
||||
WORKDIR /tinyauth
|
||||
|
||||
COPY go.mod ./
|
||||
COPY go.sum ./
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY ./cmd ./cmd
|
||||
COPY ./internal ./internal
|
||||
COPY ./main.go ./
|
||||
COPY ./air.toml ./
|
||||
|
||||
RUN mkdir -p ./internal/assets/dist && \
|
||||
echo "app running" > ./internal/assets/dist/index.html
|
||||
|
||||
RUN go install github.com/air-verse/air@v1.61.7
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["air", "-c", "air.toml"]
|
||||
34
Makefile
34
Makefile
@@ -1,34 +0,0 @@
|
||||
# Build website
|
||||
web:
|
||||
cd site; bun run build
|
||||
|
||||
# Requirements
|
||||
requirements:
|
||||
cd site; bun install
|
||||
go mod tidy
|
||||
|
||||
# Copy site assets
|
||||
assets: web
|
||||
rm -rf internal/assets/dist
|
||||
mkdir -p internal/assets/dist
|
||||
cp -r site/dist/* internal/assets/dist
|
||||
|
||||
# Run development binary
|
||||
run: assets
|
||||
go run main.go
|
||||
|
||||
# Run development binary without compiling the frontend
|
||||
run-skip-web:
|
||||
go run main.go
|
||||
|
||||
# Test
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
# Build
|
||||
build: assets
|
||||
go build -o tinyauth
|
||||
|
||||
# Build the binary without compiling the frontend
|
||||
build-skip-web:
|
||||
go build -o tinyauth
|
||||
24
air.toml
Normal file
24
air.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
root = "/tinyauth"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
pre_cmd = ["go mod tidy"]
|
||||
cmd = "go build -o ./tmp/tinyauth ."
|
||||
bin = "tmp/tinyauth"
|
||||
include_ext = ["go"]
|
||||
exclude_dir = ["internal/assets/dist"]
|
||||
exclude_regex = [".*_test\\.go"]
|
||||
stop_on_error = true
|
||||
|
||||
[color]
|
||||
main = "magenta"
|
||||
watcher = "cyan"
|
||||
build = "yellow"
|
||||
runner = "green"
|
||||
|
||||
[misc]
|
||||
clean_on_exit = true
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
@@ -13,21 +13,36 @@ services:
|
||||
image: traefik/whoami:latest
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.nginx.rule: Host(`whoami.dev.example.com`)
|
||||
traefik.http.routers.nginx.rule: Host(`whoami.example.com`)
|
||||
traefik.http.services.nginx.loadbalancer.server.port: 80
|
||||
traefik.http.routers.nginx.middlewares: tinyauth
|
||||
|
||||
tinyauth:
|
||||
container_name: tinyauth
|
||||
tinyauth-frontend:
|
||||
container_name: tinyauth-frontend
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
- SECRET=some-random-32-chars-string
|
||||
- APP_URL=http://tinyauth.dev.example.com
|
||||
- USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password
|
||||
dockerfile: site/Dockerfile.dev
|
||||
volumes:
|
||||
- ./site/src:/site/src
|
||||
ports:
|
||||
- 5173:5173
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.example.com`)
|
||||
traefik.http.services.tinyauth.loadbalancer.server.port: 3000
|
||||
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik
|
||||
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
|
||||
traefik.http.services.tinyauth.loadbalancer.server.port: 5173
|
||||
|
||||
tinyauth-backend:
|
||||
container_name: tinyauth-backend
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
env_file: .env
|
||||
volumes:
|
||||
- ./internal:/tinyauth/internal
|
||||
- ./cmd:/tinyauth/cmd
|
||||
- ./main.go:/tinyauth/main.go
|
||||
ports:
|
||||
- 3000:3000
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth-backend:3000/api/auth/traefik
|
||||
|
||||
@@ -372,7 +372,7 @@ func (api *API) SetupRoutes() {
|
||||
|
||||
api.Router.POST("/api/totp", func(c *gin.Context) {
|
||||
// Create totp struct
|
||||
var totpReq types.Totp
|
||||
var totpReq types.TotpRequest
|
||||
|
||||
// Bind JSON
|
||||
err := c.BindJSON(&totpReq)
|
||||
@@ -461,11 +461,8 @@ func (api *API) SetupRoutes() {
|
||||
})
|
||||
})
|
||||
|
||||
api.Router.GET("/api/status", func(c *gin.Context) {
|
||||
log.Debug().Msg("Checking status")
|
||||
|
||||
// Get user context
|
||||
userContext := api.Hooks.UseUserContext(c)
|
||||
api.Router.GET("/api/app", func(c *gin.Context) {
|
||||
log.Debug().Msg("Getting app context")
|
||||
|
||||
// Get configured providers
|
||||
configuredProviders := api.Providers.GetConfiguredProviders()
|
||||
@@ -475,33 +472,48 @@ func (api *API) SetupRoutes() {
|
||||
configuredProviders = append(configuredProviders, "username")
|
||||
}
|
||||
|
||||
// Fill status struct with data from user context and api config
|
||||
status := types.Status{
|
||||
Username: userContext.Username,
|
||||
IsLoggedIn: userContext.IsLoggedIn,
|
||||
Oauth: userContext.OAuth,
|
||||
Provider: userContext.Provider,
|
||||
// Create app context struct
|
||||
appContext := types.AppContext{
|
||||
Status: 200,
|
||||
Message: "Ok",
|
||||
ConfiguredProviders: configuredProviders,
|
||||
DisableContinue: api.Config.DisableContinue,
|
||||
Title: api.Config.Title,
|
||||
GenericName: api.Config.GenericName,
|
||||
TotpPending: userContext.TotpPending,
|
||||
}
|
||||
|
||||
// Return app context
|
||||
c.JSON(200, appContext)
|
||||
})
|
||||
|
||||
api.Router.GET("/api/user", func(c *gin.Context) {
|
||||
log.Debug().Msg("Getting user context")
|
||||
|
||||
// Get user context
|
||||
userContext := api.Hooks.UseUserContext(c)
|
||||
|
||||
// Create user context response
|
||||
userContextResponse := types.UserContextResponse{
|
||||
Status: 200,
|
||||
IsLoggedIn: userContext.IsLoggedIn,
|
||||
Username: userContext.Username,
|
||||
Provider: userContext.Provider,
|
||||
Oauth: userContext.OAuth,
|
||||
TotpPending: userContext.TotpPending,
|
||||
}
|
||||
|
||||
// If we are not logged in we set the status to 401 and add the WWW-Authenticate header else we set it to 200
|
||||
if !userContext.IsLoggedIn {
|
||||
log.Debug().Msg("Unauthorized")
|
||||
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
|
||||
status.Status = 401
|
||||
status.Message = "Unauthorized"
|
||||
userContextResponse.Message = "Unauthorized"
|
||||
} else {
|
||||
log.Debug().Interface("userContext", userContext).Strs("configuredProviders", configuredProviders).Bool("disableContinue", api.Config.DisableContinue).Msg("Authenticated")
|
||||
status.Status = 200
|
||||
status.Message = "Authenticated"
|
||||
log.Debug().Interface("userContext", userContext).Msg("Authenticated")
|
||||
userContextResponse.Message = "Authenticated"
|
||||
}
|
||||
|
||||
// Return data
|
||||
c.JSON(200, status)
|
||||
// Return user context
|
||||
c.JSON(200, userContextResponse)
|
||||
})
|
||||
|
||||
api.Router.GET("/api/oauth/url/:provider", func(c *gin.Context) {
|
||||
@@ -710,7 +722,12 @@ func (api *API) Run() {
|
||||
log.Info().Str("address", api.Config.Address).Int("port", api.Config.Port).Msg("Starting server")
|
||||
|
||||
// Run server
|
||||
api.Router.Run(fmt.Sprintf("%s:%d", api.Config.Address, api.Config.Port))
|
||||
err := api.Router.Run(fmt.Sprintf("%s:%d", api.Config.Address, api.Config.Port))
|
||||
|
||||
// Check error
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to start server")
|
||||
}
|
||||
}
|
||||
|
||||
// handleError logs the error and redirects to the error page (only meant for stuff the user may access does not apply for login paths)
|
||||
|
||||
@@ -2,6 +2,7 @@ package api_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -122,9 +123,9 @@ func TestLogin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test status
|
||||
func TestStatus(t *testing.T) {
|
||||
t.Log("Testing status")
|
||||
// Test user context
|
||||
func TestUserContext(t *testing.T) {
|
||||
t.Log("Testing user context")
|
||||
|
||||
// Get API
|
||||
api := getAPI(t)
|
||||
@@ -133,7 +134,7 @@ func TestStatus(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
// Create request
|
||||
req, err := http.NewRequest("GET", "/api/status", nil)
|
||||
req, err := http.NewRequest("GET", "/api/user", nil)
|
||||
|
||||
// Check if there was an error
|
||||
if err != nil {
|
||||
@@ -152,11 +153,31 @@ func TestStatus(t *testing.T) {
|
||||
// Assert
|
||||
assert.Equal(t, recorder.Code, http.StatusOK)
|
||||
|
||||
// Parse the body
|
||||
body := recorder.Body.String()
|
||||
// Read the body of the response
|
||||
body, bodyErr := io.ReadAll(recorder.Body)
|
||||
|
||||
if !strings.Contains(body, "user") {
|
||||
t.Fatalf("Expected user in body")
|
||||
// Check if there was an error
|
||||
if bodyErr != nil {
|
||||
t.Fatalf("Error getting body: %v", bodyErr)
|
||||
}
|
||||
|
||||
// Unmarshal the body into the user struct
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
var user User
|
||||
|
||||
jsonErr := json.Unmarshal(body, &user)
|
||||
|
||||
// Check if there was an error
|
||||
if jsonErr != nil {
|
||||
t.Fatalf("Error unmarshalling body: %v", jsonErr)
|
||||
}
|
||||
|
||||
// We should get the username back
|
||||
if user.Username != "user" {
|
||||
t.Fatalf("Expected user, got %s", user.Username)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ type Config struct {
|
||||
SessionExpiry int `mapstructure:"session-expiry"`
|
||||
LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"`
|
||||
Title string `mapstructure:"app-title"`
|
||||
EnvFile string `mapstructure:"env-file"`
|
||||
}
|
||||
|
||||
// UserContext is the context for the user
|
||||
@@ -138,22 +139,28 @@ type Proxy struct {
|
||||
Proxy string `uri:"proxy" binding:"required"`
|
||||
}
|
||||
|
||||
// Status response
|
||||
type Status struct {
|
||||
// User Context response is the response for the user context endpoint
|
||||
type UserContextResponse struct {
|
||||
Status int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
IsLoggedIn bool `json:"isLoggedIn"`
|
||||
Username string `json:"username"`
|
||||
Provider string `json:"provider"`
|
||||
Oauth bool `json:"oauth"`
|
||||
TotpPending bool `json:"totpPending"`
|
||||
}
|
||||
|
||||
// App Context is the response for the app context endpoint
|
||||
type AppContext struct {
|
||||
Status int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
IsLoggedIn bool `json:"isLoggedIn"`
|
||||
Username string `json:"username"`
|
||||
Provider string `json:"provider"`
|
||||
Oauth bool `json:"oauth"`
|
||||
ConfiguredProviders []string `json:"configuredProviders"`
|
||||
DisableContinue bool `json:"disableContinue"`
|
||||
Title string `json:"title"`
|
||||
GenericName string `json:"genericName"`
|
||||
TotpPending bool `json:"totpPending"`
|
||||
}
|
||||
|
||||
// Totp request
|
||||
type Totp struct {
|
||||
// Totp request is the request for the totp endpoint
|
||||
type TotpRequest struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
23
site/Dockerfile.dev
Normal file
23
site/Dockerfile.dev
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM oven/bun:1.1.45-alpine
|
||||
|
||||
WORKDIR /site
|
||||
|
||||
COPY ./site/package.json ./
|
||||
COPY ./site/bun.lockb ./
|
||||
|
||||
RUN bun install
|
||||
|
||||
COPY ./site/public ./public
|
||||
COPY ./site/src ./src
|
||||
|
||||
COPY ./site/eslint.config.js ./
|
||||
COPY ./site/index.html ./
|
||||
COPY ./site/tsconfig.json ./
|
||||
COPY ./site/tsconfig.app.json ./
|
||||
COPY ./site/tsconfig.node.json ./
|
||||
COPY ./site/vite.config.ts ./
|
||||
COPY ./site/postcss.config.cjs ./
|
||||
|
||||
EXPOSE 5173
|
||||
|
||||
ENTRYPOINT ["bun", "run", "dev"]
|
||||
42
site/src/context/app-context.tsx
Normal file
42
site/src/context/app-context.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React, { createContext, useContext } from "react";
|
||||
import axios from "axios";
|
||||
import { AppContextSchemaType } from "../schemas/app-context-schema";
|
||||
|
||||
const AppContext = createContext<AppContextSchemaType | null>(null);
|
||||
|
||||
export const AppContextProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const {
|
||||
data: userContext,
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryKey: ["appContext"],
|
||||
queryFn: async () => {
|
||||
const res = await axios.get("/api/app");
|
||||
return res.data;
|
||||
},
|
||||
});
|
||||
|
||||
if (error && !isLoading) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={userContext}>{children}</AppContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAppContext = () => {
|
||||
const context = useContext(AppContext);
|
||||
|
||||
if (context === null) {
|
||||
throw new Error("useAppContext must be used within an AppContextProvider");
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
@@ -17,7 +17,7 @@ export const UserContextProvider = ({
|
||||
} = useQuery({
|
||||
queryKey: ["userContext"],
|
||||
queryFn: async () => {
|
||||
const res = await axios.get("/api/status");
|
||||
const res = await axios.get("/api/user");
|
||||
return res.data;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import { NotFoundPage } from "./pages/not-found-page.tsx";
|
||||
import { UnauthorizedPage } from "./pages/unauthorized-page.tsx";
|
||||
import { InternalServerError } from "./pages/internal-server-error.tsx";
|
||||
import { TotpPage } from "./pages/totp-page.tsx";
|
||||
import { AppContextProvider } from "./context/app-context.tsx";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -30,20 +31,22 @@ createRoot(document.getElementById("root")!).render(
|
||||
<MantineProvider forceColorScheme="dark">
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Notifications />
|
||||
<UserContextProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<App />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/totp" element={<TotpPage />} />
|
||||
<Route path="/logout" element={<LogoutPage />} />
|
||||
<Route path="/continue" element={<ContinuePage />} />
|
||||
<Route path="/unauthorized" element={<UnauthorizedPage />} />
|
||||
<Route path="/error" element={<InternalServerError />} />
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</UserContextProvider>
|
||||
<AppContextProvider>
|
||||
<UserContextProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<App />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/totp" element={<TotpPage />} />
|
||||
<Route path="/logout" element={<LogoutPage />} />
|
||||
<Route path="/continue" element={<ContinuePage />} />
|
||||
<Route path="/unauthorized" element={<UnauthorizedPage />} />
|
||||
<Route path="/error" element={<InternalServerError />} />
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</UserContextProvider>
|
||||
</AppContextProvider>
|
||||
</QueryClientProvider>
|
||||
</MantineProvider>
|
||||
</StrictMode>,
|
||||
|
||||
@@ -5,13 +5,15 @@ import { useUserContext } from "../context/user-context";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { ReactNode } from "react";
|
||||
import { isQueryValid } from "../utils/utils";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
|
||||
export const ContinuePage = () => {
|
||||
const queryString = window.location.search;
|
||||
const params = new URLSearchParams(queryString);
|
||||
const redirectUri = params.get("redirect_uri") ?? "";
|
||||
|
||||
const { isLoggedIn, disableContinue } = useUserContext();
|
||||
const { isLoggedIn } = useUserContext();
|
||||
const { disableContinue } = useAppContext();
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return <Navigate to={`/login?redirect_uri=${redirectUri}`} />;
|
||||
|
||||
@@ -9,14 +9,15 @@ import { OAuthButtons } from "../components/auth/oauth-buttons";
|
||||
import { LoginFormValues } from "../schemas/login-schema";
|
||||
import { LoginForm } from "../components/auth/login-forn";
|
||||
import { isQueryValid } from "../utils/utils";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
|
||||
export const LoginPage = () => {
|
||||
const queryString = window.location.search;
|
||||
const params = new URLSearchParams(queryString);
|
||||
const redirectUri = params.get("redirect_uri") ?? "";
|
||||
|
||||
const { isLoggedIn, configuredProviders, title, genericName } =
|
||||
useUserContext();
|
||||
const { isLoggedIn } = useUserContext();
|
||||
const { configuredProviders, title, genericName } = useAppContext();
|
||||
|
||||
const oauthProviders = configuredProviders.filter(
|
||||
(value) => value !== "username",
|
||||
|
||||
@@ -6,9 +6,11 @@ import { useUserContext } from "../context/user-context";
|
||||
import { Navigate } from "react-router";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { capitalize } from "../utils/utils";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
|
||||
export const LogoutPage = () => {
|
||||
const { isLoggedIn, username, oauth, provider, genericName } = useUserContext();
|
||||
const { isLoggedIn, username, oauth, provider } = useUserContext();
|
||||
const { genericName } = useAppContext();
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return <Navigate to="/login" />;
|
||||
@@ -45,8 +47,9 @@ export const LogoutPage = () => {
|
||||
</Text>
|
||||
<Text>
|
||||
You are currently logged in as <Code>{username}</Code>
|
||||
{oauth && ` using ${capitalize(provider === "generic" ? genericName : provider)} OAuth`}. Click the button
|
||||
below to log out.
|
||||
{oauth &&
|
||||
` using ${capitalize(provider === "generic" ? genericName : provider)} OAuth`}
|
||||
. Click the button below to log out.
|
||||
</Text>
|
||||
<Button
|
||||
fullWidth
|
||||
|
||||
@@ -6,13 +6,15 @@ import { TotpForm } from "../components/auth/totp-form";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useAppContext } from "../context/app-context";
|
||||
|
||||
export const TotpPage = () => {
|
||||
const queryString = window.location.search;
|
||||
const params = new URLSearchParams(queryString);
|
||||
const redirectUri = params.get("redirect_uri") ?? "";
|
||||
|
||||
const { totpPending, isLoggedIn, title } = useUserContext();
|
||||
const { totpPending, isLoggedIn } = useUserContext();
|
||||
const { title } = useAppContext();
|
||||
|
||||
if (isLoggedIn) {
|
||||
return <Navigate to={`/logout`} />;
|
||||
|
||||
10
site/src/schemas/app-context-schema.ts
Normal file
10
site/src/schemas/app-context-schema.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const appContextSchema = z.object({
|
||||
configuredProviders: z.array(z.string()),
|
||||
disableContinue: z.boolean(),
|
||||
title: z.string(),
|
||||
genericName: z.string(),
|
||||
});
|
||||
|
||||
export type AppContextSchemaType = z.infer<typeof appContextSchema>;
|
||||
@@ -5,10 +5,6 @@ export const userContextSchema = z.object({
|
||||
username: z.string(),
|
||||
oauth: z.boolean(),
|
||||
provider: z.string(),
|
||||
configuredProviders: z.array(z.string()),
|
||||
disableContinue: z.boolean(),
|
||||
title: z.string(),
|
||||
genericName: z.string(),
|
||||
totpPending: z.boolean(),
|
||||
});
|
||||
|
||||
|
||||
@@ -4,4 +4,14 @@ import react from "@vitejs/plugin-react-swc";
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://tinyauth-backend:3000/api",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user