Compare commits

..

7 Commits

Author SHA1 Message Date
Stavros
ad46624bff fix: fix cli not exiting on invalid input 2025-03-09 18:38:39 +02:00
Stavros
f1c33d90cd refactor: skip all checks when disable continue is enabled 2025-03-09 18:31:11 +02:00
Stavros
10877e6f41 refactor: make totp pending expiry time fixed 2025-03-09 18:20:54 +02:00
Stavros
bd7a140676 feat: add totp logic and ui 2025-03-06 19:22:12 +02:00
Stavros
61f4848f20 refactor: split login screen and forms 2025-03-06 17:30:35 +02:00
Stavros
9f5f4adddb feat: finalize totp gen code 2025-03-06 16:41:57 +02:00
Stavros
746ce016cb wip 2025-03-04 17:51:47 +02:00
39 changed files with 573 additions and 1060 deletions

View File

@@ -1,30 +0,0 @@
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

58
.github/workflows/alpha-release.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Alpha Release
on:
workflow_dispatch:
inputs:
alpha:
description: "Alpha version (e.g. 1, 2, 3)"
required: true
jobs:
get-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get tag
id: tag
run: echo "name=$(cat internal/assets/version)-alpha.${{ github.event.inputs.alpha }}" >> $GITHUB_OUTPUT
build-docker:
needs: get-tag
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/arm64, linux/amd64
tags: ghcr.io/${{ github.repository_owner }}/tinyauth:${{ needs.get-tag.outputs.tag }}
alpha-release:
needs: [get-tag, build-docker]
runs-on: ubuntu-latest
steps:
- name: Create alpha release
uses: softprops/action-gh-release@v2
with:
prerelease: true
tag_name: ${{ needs.get-tag.outputs.tag }}

58
.github/workflows/beta-release.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Beta Release
on:
workflow_dispatch:
inputs:
alpha:
description: "Beta version (e.g. 1, 2, 3)"
required: true
jobs:
get-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get tag
id: tag
run: echo "name=$(cat internal/assets/version)-beta.${{ github.event.inputs.alpha }}" >> $GITHUB_OUTPUT
build-docker:
needs: get-tag
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/arm64, linux/amd64
tags: ghcr.io/${{ github.repository_owner }}/tinyauth:${{ needs.get-tag.outputs.tag }}
beta-release:
needs: [get-tag, build-docker]
runs-on: ubuntu-latest
steps:
- name: Create beta release
uses: softprops/action-gh-release@v2
with:
prerelease: true
tag_name: ${{ needs.get-tag.outputs.tag }}

View File

@@ -1,22 +1,32 @@
name: Release name: Release
on: on:
workflow_dispatch: workflow_dispatch:
push:
tags:
- "v*"
jobs: jobs:
build: get-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get tag
id: tag
run: echo "name=$(cat internal/assets/version)" >> $GITHUB_OUTPUT
build-docker:
needs: get-tag
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Docker meta - name: Set up QEMU
id: meta uses: docker/setup-qemu-action@v3
uses: docker/metadata-action@v5
with: - name: Set up Docker Buildx
images: ghcr.io/${{ github.repository_owner }}/tinyauth uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
@@ -25,112 +35,21 @@ jobs:
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
id: build
with: with:
platforms: linux/amd64 context: .
labels: ${{ steps.meta.outputs.labels }} push: true
tags: ghcr.io/${{ github.repository_owner }}/tinyauth platforms: linux/arm64, linux/amd64
outputs: type=image,push-by-digest=true,name-canonical=true,push=true tags: ghcr.io/${{ github.repository_owner }}/tinyauth:${{ needs.get-tag.outputs.tag }}, ghcr.io/${{ github.repository_owner }}/tinyauth:latest
- name: Export digest release:
run: | needs: [get-tag, build-docker]
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-linux-amd64
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
build-arm:
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
id: build
with:
platforms: linux/arm64
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/${{ github.repository_owner }}/tinyauth
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-linux-arm64
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- build
- build-arm
steps: steps:
- name: Download digests - name: Create release
uses: actions/download-artifact@v4 uses: softprops/action-gh-release@v2
with: with:
path: ${{ runner.temp }}/digests prerelease: false
pattern: digests-* make_latest: false
merge-multiple: true tag_name: ${{ needs.get-tag.outputs.tag }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/tinyauth
tags: |
type=semver,pattern=v{{version}}
type=semver,pattern=v{{major}}
type=semver,pattern=v{{major}}.{{minor}}
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth@sha256:%s ' *)

6
.gitignore vendored
View File

@@ -19,9 +19,3 @@ secret_oauth.txt
# apple stuff # apple stuff
.DS_Store .DS_Store
# env
.env
# tmp directory
tmp

View File

@@ -1,6 +1,6 @@
# Contributing # Contributing
Contributing is relatively easy, you just need to follow the steps carefully and you will be up and running with a development server in less than 5 minutes. Contributing is relatively easy.
## Requirements ## Requirements
@@ -20,37 +20,62 @@ cd tinyauth
## Install requirements ## Install requirements
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: Now it's time to install the requirements, firstly the Go ones:
```sh ```sh
go mod tidy go mod download
``` ```
You also need to download the frontend requirements, this can be done like so: And now the site ones:
```sh ```sh
cd site/ cd site
bun install bun i
``` ```
## Create your `.env` file ## Developing locally
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. In order to develop the app locally you need to build the frontend and copy it to the assets folder in order for Go to embed it and host it. In order to build the frontend run:
## Developing ```sh
cd site
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: bun run build
cd ..
```
*.dev.example.com -> 127.0.0.1
dev.example.com -> 127.0.0.1
``` ```
Then you can just make sure the domains are correct in the example docker compose file and run: Copy it to the assets folder:
```sh
rm -rf internal/assets/dist
cp -r site/dist internal/assets/dist
```
Finally either run the app with:
```sh
go run main.go
```
Or build it with:
```sh
go build
```
> [!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:
```
*.dev.local -> 127.0.0.1
dev.local -> 127.0.0.1
```
Then I can just make sure the domains are correct in the example docker compose file and do:
```sh ```sh
docker compose -f docker-compose.dev.yml up --build docker compose -f docker-compose.dev.yml up --build
``` ```
> [!NOTE]
> I would recommend copying the example `docker-compose.dev.yml` into a `docker-compose.test.yml` file, so as you don't accidentally commit any sensitive information.

View File

@@ -1,23 +0,0 @@
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 ./docs ./docs
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"]

View File

@@ -1,2 +0,0 @@
github: steveiliop56
buy_me_a_coffee: steveiliop56

25
Makefile Normal file
View File

@@ -0,0 +1,25 @@
# Build website
web:
cd site; bun run build
# 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
# Test
test:
go test ./...
# Build
build: assets
go build -o tinyauth
# Build no site
build-skip-web:
go build -o tinyauth

View File

@@ -28,11 +28,11 @@ I just made a Discord server for Tinyauth! It is not only for Tinyauth but gener
## Getting Started ## Getting Started
You can easily get started with tinyauth by following the guide on the [documentation](https://tinyauth.app/docs/getting-started.html). There is also an available [docker compose file](./docker-compose.example.yml) that has traefik, nginx and tinyauth to demonstrate its capabilities. You can easily get started with tinyauth by following the guide on the [documentation](https://tinyauth.doesmycode.work/docs/getting-started.html). There is also an available [docker compose file](./docker-compose.example.yml) that has traefik, nginx and tinyauth to demonstrate its capabilities.
## Documentation ## Documentation
You can find documentation and guides on all available configuration of tinyauth [here](https://tinyauth.app). You can find documentation and guides on all available configuration of tinyauth [here](https://tinyauth.doesmycode.work).
## Contributing ## Contributing
@@ -42,14 +42,6 @@ All contributions to the codebase are welcome! If you have any recommendations o
Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. For more information about the license check the [license](./LICENSE) file. Tinyauth is licensed under the GNU General Public License v3.0. TL;DR — You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. For more information about the license check the [license](./LICENSE) file.
## Sponsors
Thanks a lot to the following people for providing me with more coffee:
| <img height="64" src="https://avatars.githubusercontent.com/u/47644445?v=4" alt="Nicolas"> | <img height="64" src="https://avatars.githubusercontent.com/u/4255748?v=4" alt="Erwin"> |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| <div align="center"><a href="https://github.com/nicotsx">Nicolas</a></div> | <div align="center"><a href="https://github.com/erwinkramer">Erwin</a></div> |
## Acknowledgements ## Acknowledgements
Credits for the logo of this app go to: Credits for the logo of this app go to:

View File

@@ -1,24 +0,0 @@
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

View File

@@ -3,8 +3,8 @@
"embeds": [ "embeds": [
{ {
"title": "Welcome to Tinyauth Discord!", "title": "Welcome to Tinyauth Discord!",
"description": "Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic OAuth provider to all of your docker apps.\n\n**Information**\n\n• Github: <https://github.com/steveiliop56/tinyauth>\n• Website: <https://tinyauth.app>", "description": "Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic OAuth provider to all of your docker apps.\n\n**Information**\n\n• Github: <https://github.com/steveiliop56/tinyauth>\n• Website: <https://tinyauth.doesmycode.work>",
"url": "https://tinyauth.app", "url": "https://tinyauth.doesmycode.work",
"color": 7002085, "color": 7002085,
"author": { "author": {
"name": "Tinyauth" "name": "Tinyauth"
@@ -12,7 +12,7 @@
"footer": { "footer": {
"text": "Updated at" "text": "Updated at"
}, },
"timestamp": "2025-03-10T19:00:00.000Z", "timestamp": "2025-02-06T22:00:00.000Z",
"thumbnail": { "thumbnail": {
"url": "https://github.com/steveiliop56/tinyauth/blob/main/site/public/logo.png?raw=true" "url": "https://github.com/steveiliop56/tinyauth/blob/main/site/public/logo.png?raw=true"
} }

View File

@@ -2,7 +2,6 @@ package cmd
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"strings" "strings"
"time" "time"
@@ -12,7 +11,6 @@ import (
"tinyauth/internal/assets" "tinyauth/internal/assets"
"tinyauth/internal/auth" "tinyauth/internal/auth"
"tinyauth/internal/docker" "tinyauth/internal/docker"
"tinyauth/internal/handlers"
"tinyauth/internal/hooks" "tinyauth/internal/hooks"
"tinyauth/internal/providers" "tinyauth/internal/providers"
"tinyauth/internal/types" "tinyauth/internal/types"
@@ -108,18 +106,8 @@ var rootCmd = &cobra.Command{
// Create hooks service // Create hooks service
hooks := hooks.NewHooks(auth, providers) hooks := hooks.NewHooks(auth, providers)
// Create doman // Create API
domain, domainErr := utils.GetRootURL(config.AppURL) api := api.NewAPI(types.APIConfig{
if domainErr != nil {
log.Fatal().Err(domainErr).Msg("Failed to get domain")
os.Exit(1)
}
log.Info().Str("domain", domain).Msg("Using domain for cookies")
// Create api config
apiConfig := types.APIConfig{
Port: config.Port, Port: config.Port,
Address: config.Address, Address: config.Address,
Secret: config.Secret, Secret: config.Secret,
@@ -129,14 +117,7 @@ var rootCmd = &cobra.Command{
SessionExpiry: config.SessionExpiry, SessionExpiry: config.SessionExpiry,
Title: config.Title, Title: config.Title,
GenericName: config.GenericName, GenericName: config.GenericName,
Domain: fmt.Sprintf(".%s", domain), }, hooks, auth, providers)
}
// Create handlers
apiHandlers := handlers.NewHandlers(apiConfig, auth, hooks)
// Create API
api := api.NewAPI(apiConfig, hooks, auth, providers, apiHandlers)
// Setup routes // Setup routes
api.Init() api.Init()

View File

@@ -8,42 +8,27 @@ services:
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
whoami: nginx:
container_name: whoami container_name: nginx
image: traefik/whoami:latest image: nginx:latest
labels: labels:
traefik.enable: true traefik.enable: true
traefik.http.routers.nginx.rule: Host(`whoami.example.com`) traefik.http.routers.nginx.rule: Host(`nginx.dev.local`)
traefik.http.services.nginx.loadbalancer.server.port: 80 traefik.http.services.nginx.loadbalancer.server.port: 80
traefik.http.routers.nginx.middlewares: tinyauth traefik.http.routers.nginx.middlewares: tinyauth
tinyauth-frontend: tinyauth:
container_name: tinyauth-frontend container_name: tinyauth
build: build:
context: . context: .
dockerfile: site/Dockerfile.dev dockerfile: Dockerfile
volumes: environment:
- ./site/src:/site/src - SECRET=some-random-32-chars-string
ports: - APP_URL=http://tinyauth.dev.local
- 5173:5173 - USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password
labels: labels:
traefik.enable: true traefik.enable: true
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`) traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`)
traefik.http.services.tinyauth.loadbalancer.server.port: 5173 traefik.http.services.tinyauth.loadbalancer.server.port: 3000
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik
tinyauth-backend: traefik.http.middlewares.tinyauth.forwardauth.authResponseHeaders: X-Tinyauth-User
container_name: tinyauth-backend
build:
context: .
dockerfile: Dockerfile.dev
env_file: .env
volumes:
- ./internal:/tinyauth/internal
- ./cmd:/tinyauth/cmd
- ./docs:/tinyauth/docs
- ./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

View File

@@ -8,18 +8,18 @@ services:
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
whoami: nginx:
container_name: whoami container_name: nginx
image: traefik/whoami:latest image: nginx:latest
labels: labels:
traefik.enable: true traefik.enable: true
traefik.http.routers.nginx.rule: Host(`whoami.example.com`) traefik.http.routers.nginx.rule: Host(`nginx.example.com`)
traefik.http.services.nginx.loadbalancer.server.port: 80 traefik.http.services.nginx.loadbalancer.server.port: 80
traefik.http.routers.nginx.middlewares: tinyauth traefik.http.routers.nginx.middlewares: tinyauth
tinyauth: tinyauth:
container_name: tinyauth container_name: tinyauth
image: ghcr.io/steveiliop56/tinyauth:v3 image: ghcr.io/steveiliop56/tinyauth:latest
environment: environment:
- SECRET=some-random-32-chars-string - SECRET=some-random-32-chars-string
- APP_URL=https://tinyauth.example.com - APP_URL=https://tinyauth.example.com
@@ -29,3 +29,4 @@ services:
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`) traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
traefik.http.services.tinyauth.loadbalancer.server.port: 3000 traefik.http.services.tinyauth.loadbalancer.server.port: 3000
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik
traefik.http.middlewares.tinyauth.forwardauth.authResponseHeaders: X-Tinyauth-User

View File

@@ -1,92 +0,0 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/auth/logout": {
"get": {
"description": "Log the user out by invalidating the session cookie",
"produces": [
"application/json"
],
"tags": [
"auth"
],
"summary": "Logout",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.SimpleResponse"
}
}
}
}
},
"/healthcheck": {
"get": {
"description": "Simple health check",
"produces": [
"application/json"
],
"tags": [
"health"
],
"summary": "Health Check",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.SimpleResponse"
}
}
}
}
}
},
"definitions": {
"types.SimpleResponse": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "OK"
},
"status": {
"type": "integer",
"example": 200
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "/api",
Schemes: []string{},
Title: "Tinyauth API",
Description: "Documentation for the Tinyauth API",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

View File

@@ -1,67 +0,0 @@
{
"swagger": "2.0",
"info": {
"description": "Documentation for the Tinyauth API",
"title": "Tinyauth API",
"contact": {},
"version": "1.0"
},
"basePath": "/api",
"paths": {
"/auth/logout": {
"get": {
"description": "Log the user out by invalidating the session cookie",
"produces": [
"application/json"
],
"tags": [
"auth"
],
"summary": "Logout",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.SimpleResponse"
}
}
}
}
},
"/healthcheck": {
"get": {
"description": "Simple health check",
"produces": [
"application/json"
],
"tags": [
"health"
],
"summary": "Health Check",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.SimpleResponse"
}
}
}
}
}
},
"definitions": {
"types.SimpleResponse": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "OK"
},
"status": {
"type": "integer",
"example": 200
}
}
}
}
}

View File

@@ -1,44 +0,0 @@
basePath: /api
definitions:
types.SimpleResponse:
properties:
message:
example: OK
type: string
status:
example: 200
type: integer
type: object
info:
contact: {}
description: Documentation for the Tinyauth API
title: Tinyauth API
version: "1.0"
paths:
/auth/logout:
get:
description: Log the user out by invalidating the session cookie
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/types.SimpleResponse'
summary: Logout
tags:
- auth
/healthcheck:
get:
description: Simple health check
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/types.SimpleResponse'
summary: Health Check
tags:
- health
swagger: "2.0"

41
go.mod
View File

@@ -5,34 +5,23 @@ go 1.23.2
require ( require (
github.com/gin-contrib/sessions v1.0.2 github.com/gin-contrib/sessions v1.0.2
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/go-playground/validator/v10 v10.25.0 github.com/go-playground/validator/v10 v10.24.0
github.com/google/go-querystring v1.1.0 github.com/google/go-querystring v1.1.0
github.com/mdp/qrterminal/v3 v3.2.0
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.33.0
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.19.0
github.com/swaggo/swag v1.16.4 golang.org/x/crypto v0.32.0
golang.org/x/crypto v0.36.0
) )
require ( require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/mdp/qrterminal/v3 v3.2.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/moby/term v0.5.2 // indirect github.com/moby/term v0.5.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/swaggo/files v1.0.1 // indirect
github.com/swaggo/gin-swagger v1.6.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect
golang.org/x/term v0.30.0 // indirect golang.org/x/term v0.28.0 // indirect
golang.org/x/tools v0.31.0 // indirect
gotest.tools/v3 v3.5.2 // indirect gotest.tools/v3 v3.5.2 // indirect
rsc.io/qr v0.2.0 // indirect rsc.io/qr v0.2.0 // indirect
) )
@@ -42,8 +31,8 @@ require (
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/boombuler/barcode v1.0.2 // indirect github.com/boombuler/barcode v1.0.2 // indirect
github.com/bytedance/sonic v1.13.1 // indirect github.com/bytedance/sonic v1.12.7 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/catppuccin/go v0.2.0 // indirect github.com/catppuccin/go v0.2.0 // indirect
github.com/charmbracelet/bubbles v0.20.0 // indirect github.com/charmbracelet/bubbles v0.20.0 // indirect
github.com/charmbracelet/bubbletea v1.1.0 // indirect github.com/charmbracelet/bubbletea v1.1.0 // indirect
@@ -52,7 +41,7 @@ require (
github.com/charmbracelet/x/ansi v0.2.3 // indirect github.com/charmbracelet/x/ansi v0.2.3 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.0 // indirect github.com/charmbracelet/x/term v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v27.5.1+incompatible github.com/docker/docker v27.5.1+incompatible
github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
@@ -67,7 +56,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
@@ -75,7 +64,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.7 github.com/magiconair/properties v1.8.7
@@ -112,14 +101,14 @@ require (
go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.15.0 // indirect golang.org/x/arch v0.13.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.37.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.25.0 golang.org/x/oauth2 v0.25.0
golang.org/x/sync v0.12.0 // indirect golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect google.golang.org/protobuf v1.36.3 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

98
go.sum
View File

@@ -1,7 +1,5 @@
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
@@ -13,11 +11,11 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4= github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
@@ -36,8 +34,8 @@ github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
@@ -78,24 +76,16 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -121,15 +111,13 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -142,8 +130,6 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
@@ -236,19 +222,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
@@ -271,74 +250,53 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -350,8 +308,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -10,12 +10,10 @@ import (
"time" "time"
"tinyauth/internal/assets" "tinyauth/internal/assets"
"tinyauth/internal/auth" "tinyauth/internal/auth"
"tinyauth/internal/handlers"
"tinyauth/internal/hooks" "tinyauth/internal/hooks"
"tinyauth/internal/providers" "tinyauth/internal/providers"
"tinyauth/internal/types" "tinyauth/internal/types"
"tinyauth/internal/utils"
docs "tinyauth/docs"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/sessions/cookie"
@@ -23,17 +21,14 @@ import (
"github.com/google/go-querystring/query" "github.com/google/go-querystring/query"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
swaggerfiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
) )
func NewAPI(config types.APIConfig, hooks *hooks.Hooks, auth *auth.Auth, providers *providers.Providers, handlers *handlers.Handlers) *API { func NewAPI(config types.APIConfig, hooks *hooks.Hooks, auth *auth.Auth, providers *providers.Providers) *API {
return &API{ return &API{
Config: config, Config: config,
Hooks: hooks, Hooks: hooks,
Auth: auth, Auth: auth,
Providers: providers, Providers: providers,
Handlers: handlers,
} }
} }
@@ -43,15 +38,9 @@ type API struct {
Hooks *hooks.Hooks Hooks *hooks.Hooks
Auth *auth.Auth Auth *auth.Auth
Providers *providers.Providers Providers *providers.Providers
Handlers *handlers.Handlers
Domain string Domain string
} }
// @title Tinyauth API
// @version 1.0
// @description Documentation for the Tinyauth API
// @BasePath /api
func (api *API) Init() { func (api *API) Init() {
// Disable gin logs // Disable gin logs
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
@@ -60,7 +49,6 @@ func (api *API) Init() {
log.Debug().Msg("Setting up router") log.Debug().Msg("Setting up router")
router := gin.New() router := gin.New()
router.Use(zerolog()) router.Use(zerolog())
router.RedirectTrailingSlash = true
// Read UI assets // Read UI assets
log.Debug().Msg("Setting up assets") log.Debug().Msg("Setting up assets")
@@ -78,6 +66,19 @@ func (api *API) Init() {
log.Debug().Msg("Setting up cookie store") log.Debug().Msg("Setting up cookie store")
store := cookie.NewStore([]byte(api.Config.Secret)) store := cookie.NewStore([]byte(api.Config.Secret))
// Get domain to use for session cookies
log.Debug().Msg("Getting domain")
domain, domainErr := utils.GetRootURL(api.Config.AppURL)
if domainErr != nil {
log.Fatal().Err(domainErr).Msg("Failed to get domain")
os.Exit(1)
}
log.Info().Str("domain", domain).Msg("Using domain for cookies")
api.Domain = fmt.Sprintf(".%s", domain)
// Use session middleware // Use session middleware
store.Options(sessions.Options{ store.Options(sessions.Options{
Domain: api.Domain, Domain: api.Domain,
@@ -89,15 +90,6 @@ func (api *API) Init() {
router.Use(sessions.Sessions("tinyauth", store)) router.Use(sessions.Sessions("tinyauth", store))
// Configure swagger
docs.SwaggerInfo.BasePath = "/api"
// Swagger middleware
router.GET("/api/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
router.GET("/api/swagger", func(ctx *gin.Context) {
ctx.Redirect(http.StatusPermanentRedirect, "/api/swagger/index.html")
})
// UI middleware // UI middleware
router.Use(func(c *gin.Context) { router.Use(func(c *gin.Context) {
// If not an API request, serve the UI // If not an API request, serve the UI
@@ -122,9 +114,169 @@ func (api *API) Init() {
} }
func (api *API) SetupRoutes() { func (api *API) SetupRoutes() {
api.Router.GET("/api/healthcheck", api.Handlers.HealthCheck) api.Router.GET("/api/auth/:proxy", func(c *gin.Context) {
api.Router.GET("/api/auth/logout", api.Handlers.Logout) // Create struct for proxy
api.Router.GET("/api/auth", api.Handlers.CheckAuth) var proxy types.Proxy
// Bind URI
bindErr := c.BindUri(&proxy)
// Handle error
if bindErr != nil {
log.Error().Err(bindErr).Msg("Failed to bind URI")
c.JSON(400, gin.H{
"status": 400,
"message": "Bad Request",
})
return
}
log.Debug().Interface("proxy", proxy.Proxy).Msg("Got proxy")
// Check if using basic auth
_, _, basicAuth := c.Request.BasicAuth()
// Check if auth is enabled
authEnabled, authEnabledErr := api.Auth.AuthEnabled(c)
// Handle error
if authEnabledErr != nil {
// Return 500 if nginx is the proxy or if the request is using basic auth
if proxy.Proxy == "nginx" || basicAuth {
log.Error().Err(authEnabledErr).Msg("Failed to check if auth is enabled")
c.JSON(500, gin.H{
"status": 500,
"message": "Internal Server Error",
})
return
}
// Return the internal server error page
if api.handleError(c, "Failed to check if auth is enabled", authEnabledErr) {
return
}
}
// If auth is not enabled, return 200
if !authEnabled {
// The user is allowed to access the app
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
})
// Stop further processing
return
}
// Get user context
userContext := api.Hooks.UseUserContext(c)
// Get headers
uri := c.Request.Header.Get("X-Forwarded-Uri")
proto := c.Request.Header.Get("X-Forwarded-Proto")
host := c.Request.Header.Get("X-Forwarded-Host")
// Check if user is logged in
if userContext.IsLoggedIn {
log.Debug().Msg("Authenticated")
// Check if user is allowed to access subdomain, if request is nginx.example.com the subdomain (resource) is nginx
appAllowed, appAllowedErr := api.Auth.ResourceAllowed(c, userContext)
// Check if there was an error
if appAllowedErr != nil {
// Return 500 if nginx is the proxy or if the request is using basic auth
if proxy.Proxy == "nginx" || basicAuth {
log.Error().Err(appAllowedErr).Msg("Failed to check if app is allowed")
c.JSON(500, gin.H{
"status": 500,
"message": "Internal Server Error",
})
return
}
// Return the internal server error page
if api.handleError(c, "Failed to check if app is allowed", appAllowedErr) {
return
}
}
log.Debug().Bool("appAllowed", appAllowed).Msg("Checking if app is allowed")
// The user is not allowed to access the app
if !appAllowed {
log.Warn().Str("username", userContext.Username).Str("host", host).Msg("User not allowed")
// Return 401 if nginx is the proxy or if the request is using an Authorization header
if proxy.Proxy == "nginx" || basicAuth {
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
c.JSON(401, gin.H{
"status": 401,
"message": "Unauthorized",
})
return
}
// Build query
queries, queryErr := query.Values(types.UnauthorizedQuery{
Username: userContext.Username,
Resource: strings.Split(host, ".")[0],
})
// Handle error (no need to check for nginx/headers since we are sure we are using caddy/traefik)
if api.handleError(c, "Failed to build query", queryErr) {
return
}
// We are using caddy/traefik so redirect
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", api.Config.AppURL, queries.Encode()))
// Stop further processing
return
}
// Set the user header
c.Header("X-Tinyauth-User", userContext.Username)
// The user is allowed to access the app
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
})
// Stop further processing
return
}
// The user is not logged in
log.Debug().Msg("Unauthorized")
// Return 401 if nginx is the proxy or if the request is using an Authorization header
if proxy.Proxy == "nginx" || basicAuth {
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
c.JSON(401, gin.H{
"status": 401,
"message": "Unauthorized",
})
return
}
// Build query
queries, queryErr := query.Values(types.LoginQuery{
RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri),
})
// Handle error (no need to check for nginx/headers since we are sure we are using caddy/traefik)
if api.handleError(c, "Failed to build query", queryErr) {
return
}
log.Debug().Interface("redirect_uri", fmt.Sprintf("%s://%s%s", proto, host, uri)).Msg("Redirecting to login")
// Redirect to login
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/?%s", api.Config.AppURL, queries.Encode()))
})
api.Router.POST("/api/login", func(c *gin.Context) { api.Router.POST("/api/login", func(c *gin.Context) {
// Create login struct // Create login struct
@@ -210,7 +362,7 @@ func (api *API) SetupRoutes() {
api.Router.POST("/api/totp", func(c *gin.Context) { api.Router.POST("/api/totp", func(c *gin.Context) {
// Create totp struct // Create totp struct
var totpReq types.TotpRequest var totpReq types.Totp
// Bind JSON // Bind JSON
err := c.BindJSON(&totpReq) err := c.BindJSON(&totpReq)
@@ -281,8 +433,29 @@ func (api *API) SetupRoutes() {
}) })
}) })
api.Router.GET("/api/app", func(c *gin.Context) { api.Router.POST("/api/logout", func(c *gin.Context) {
log.Debug().Msg("Getting app context") log.Debug().Msg("Logging out")
// Delete session cookie
api.Auth.DeleteSessionCookie(c)
log.Debug().Msg("Cleaning up redirect cookie")
// Clean up redirect cookie if it exists
c.SetCookie("tinyauth_redirect_uri", "", -1, "/", api.Domain, api.Config.CookieSecure, true)
// Return logged out
c.JSON(200, gin.H{
"status": 200,
"message": "Logged out",
})
})
api.Router.GET("/api/status", func(c *gin.Context) {
log.Debug().Msg("Checking status")
// Get user context
userContext := api.Hooks.UseUserContext(c)
// Get configured providers // Get configured providers
configuredProviders := api.Providers.GetConfiguredProviders() configuredProviders := api.Providers.GetConfiguredProviders()
@@ -292,48 +465,33 @@ func (api *API) SetupRoutes() {
configuredProviders = append(configuredProviders, "username") configuredProviders = append(configuredProviders, "username")
} }
// Create app context struct // Fill status struct with data from user context and api config
appContext := types.AppContext{ status := types.Status{
Status: 200, Username: userContext.Username,
Message: "Ok", IsLoggedIn: userContext.IsLoggedIn,
Oauth: userContext.OAuth,
Provider: userContext.Provider,
ConfiguredProviders: configuredProviders, ConfiguredProviders: configuredProviders,
DisableContinue: api.Config.DisableContinue, DisableContinue: api.Config.DisableContinue,
Title: api.Config.Title, Title: api.Config.Title,
GenericName: api.Config.GenericName, 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 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 { if !userContext.IsLoggedIn {
log.Debug().Msg("Unauthorized") log.Debug().Msg("Unauthorized")
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"") c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
userContextResponse.Message = "Unauthorized" status.Status = 401
status.Message = "Unauthorized"
} else { } else {
log.Debug().Interface("userContext", userContext).Msg("Authenticated") log.Debug().Interface("userContext", userContext).Strs("configuredProviders", configuredProviders).Bool("disableContinue", api.Config.DisableContinue).Msg("Authenticated")
userContextResponse.Message = "Authenticated" status.Status = 200
status.Message = "Authenticated"
} }
// Return user context // Return data
c.JSON(200, userContextResponse) c.JSON(200, status)
}) })
api.Router.GET("/api/oauth/url/:provider", func(c *gin.Context) { api.Router.GET("/api/oauth/url/:provider", func(c *gin.Context) {
@@ -528,18 +686,21 @@ func (api *API) SetupRoutes() {
// Redirect to continue with the redirect URI // Redirect to continue with the redirect URI
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/continue?%s", api.Config.AppURL, redirectQuery.Encode())) c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/continue?%s", api.Config.AppURL, redirectQuery.Encode()))
}) })
// Simple healthcheck
api.Router.GET("/api/healthcheck", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": 200,
"message": "OK",
})
})
} }
func (api *API) Run() { func (api *API) Run() {
log.Info().Str("address", api.Config.Address).Int("port", api.Config.Port).Msg("Starting server") log.Info().Str("address", api.Config.Address).Int("port", api.Config.Port).Msg("Starting server")
// Run server // Run server
err := api.Router.Run(fmt.Sprintf("%s:%d", api.Config.Address, api.Config.Port)) 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) // handleError logs the error and redirects to the error page (only meant for stuff the user may access does not apply for login paths)

View File

@@ -2,7 +2,6 @@ package api_test
import ( import (
"encoding/json" "encoding/json"
"io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
@@ -10,7 +9,6 @@ import (
"tinyauth/internal/api" "tinyauth/internal/api"
"tinyauth/internal/auth" "tinyauth/internal/auth"
"tinyauth/internal/docker" "tinyauth/internal/docker"
"tinyauth/internal/handlers"
"tinyauth/internal/hooks" "tinyauth/internal/hooks"
"tinyauth/internal/providers" "tinyauth/internal/providers"
"tinyauth/internal/types" "tinyauth/internal/types"
@@ -68,11 +66,8 @@ func getAPI(t *testing.T) *api.API {
// Create hooks service // Create hooks service
hooks := hooks.NewHooks(auth, providers) hooks := hooks.NewHooks(auth, providers)
// Create handlers
apiHandlers := handlers.NewHandlers(apiConfig)
// Create API // Create API
api := api.NewAPI(apiConfig, hooks, auth, providers, apiHandlers) api := api.NewAPI(apiConfig, hooks, auth, providers)
// Setup routes // Setup routes
api.Init() api.Init()
@@ -127,9 +122,9 @@ func TestLogin(t *testing.T) {
} }
} }
// Test user context // Test status
func TestUserContext(t *testing.T) { func TestStatus(t *testing.T) {
t.Log("Testing user context") t.Log("Testing status")
// Get API // Get API
api := getAPI(t) api := getAPI(t)
@@ -138,7 +133,7 @@ func TestUserContext(t *testing.T) {
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
// Create request // Create request
req, err := http.NewRequest("GET", "/api/user", nil) req, err := http.NewRequest("GET", "/api/status", nil)
// Check if there was an error // Check if there was an error
if err != nil { if err != nil {
@@ -157,31 +152,11 @@ func TestUserContext(t *testing.T) {
// Assert // Assert
assert.Equal(t, recorder.Code, http.StatusOK) assert.Equal(t, recorder.Code, http.StatusOK)
// Read the body of the response // Parse the body
body, bodyErr := io.ReadAll(recorder.Body) body := recorder.Body.String()
// Check if there was an error if !strings.Contains(body, "user") {
if bodyErr != nil { t.Fatalf("Expected user in body")
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)
} }
} }

View File

@@ -1 +1 @@
v3.1.0 v3.0.1

View File

@@ -162,10 +162,7 @@ func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext) (bo
// Check if resource is allowed // Check if resource is allowed
allowed, allowedErr := auth.Docker.ContainerAction(appId, func(labels types.TinyauthLabels) (bool, error) { allowed, allowedErr := auth.Docker.ContainerAction(appId, func(labels types.TinyauthLabels) (bool, error) {
// If the container has an oauth whitelist, check if the user is in it // If the container has an oauth whitelist, check if the user is in it
if context.OAuth { if context.OAuth && len(labels.OAuthWhitelist) != 0 {
if len(labels.OAuthWhitelist) == 0 {
return true, nil
}
log.Debug().Msg("Checking OAuth whitelist") log.Debug().Msg("Checking OAuth whitelist")
if slices.Contains(labels.OAuthWhitelist, context.Username) { if slices.Contains(labels.OAuthWhitelist, context.Username) {
return true, nil return true, nil

View File

@@ -1,209 +0,0 @@
package handlers
import (
"fmt"
"net/http"
"strings"
"tinyauth/internal/auth"
"tinyauth/internal/hooks"
"tinyauth/internal/types"
"github.com/gin-gonic/gin"
"github.com/google/go-querystring/query"
"github.com/rs/zerolog/log"
)
func NewHandlers(config types.APIConfig, auth *auth.Auth, hooks *hooks.Hooks) *Handlers {
return &Handlers{
Config: config,
Auth: auth,
Hooks: hooks,
}
}
type Handlers struct {
Config types.APIConfig
Auth *auth.Auth
Hooks *hooks.Hooks
}
// @Summary Health Check
// @Description Simple health check
// @Tags health
// @Produce json
// @Success 200 {object} types.HealthCheckResponse
// @Router /healthcheck [get]
func (h *Handlers) HealthCheck(c *gin.Context) {
c.JSON(200, gin.H{
"status": 200,
"message": "OK",
})
}
// @Summary Logout
// @Description Log the user out by invalidating the session cookie
// @Tags auth
// @Produce json
// @Success 200 {object} types.LogoutResponse
// @Router /auth/logout [get]
func (h *Handlers) Logout(c *gin.Context) {
log.Debug().Msg("Logging out")
h.Auth.DeleteSessionCookie(c)
log.Debug().Msg("Cleaning up redirect cookie")
c.SetCookie("tinyauth_redirect_uri", "", -1, "/", h.Config.Domain, h.Config.CookieSecure, true)
c.JSON(200, gin.H{
"status": 200,
"message": "Logged out",
})
}
// @Summary Auth Check (Traefik)
// @Description Check the authentication status of the user and redirect to the login page if not authenticated
// @Tags authn
// @Produce json
// @Success 302
// @Router /api/auth/traefik [get]
func (h *Handlers) CheckAuth(c *gin.Context) {
var proxy types.Proxy
err := c.BindUri(&proxy)
if err != nil {
log.Error().Err(err).Msg("Failed to bind URI")
c.JSON(400, gin.H{
"status": 400,
"message": "Bad Request",
})
return
}
isBrowser := strings.Contains(c.Request.Header.Get("Accept"), "text/html")
if isBrowser {
log.Debug().Msg("Request is most likely coming from a browser")
} else {
log.Debug().Msg("Request is most likely not coming from a browser")
}
log.Debug().Interface("proxy", proxy.Proxy).Msg("Got proxy")
authEnabled, err := h.Auth.AuthEnabled(c)
if err != nil {
log.Error().Err(err).Msg("Failed to check if auth is enabled")
if proxy.Proxy == "nginx" || !isBrowser {
c.JSON(500, gin.H{
"status": 500,
"message": "Internal Server Error",
})
return
}
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
if !authEnabled {
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
})
return
}
userContext := h.Hooks.UseUserContext(c)
uri := c.Request.Header.Get("X-Forwarded-Uri")
proto := c.Request.Header.Get("X-Forwarded-Proto")
host := c.Request.Header.Get("X-Forwarded-Host")
if userContext.IsLoggedIn {
log.Debug().Msg("Authenticated")
appAllowed, err := h.Auth.ResourceAllowed(c, userContext)
if err != nil {
log.Error().Err(err).Msg("Failed to check if app is allowed")
if proxy.Proxy == "nginx" || !isBrowser {
c.JSON(500, gin.H{
"status": 500,
"message": "Internal Server Error",
})
return
}
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
log.Debug().Bool("appAllowed", appAllowed).Msg("Checking if app is allowed")
if !appAllowed {
log.Warn().Str("username", userContext.Username).Str("host", host).Msg("User not allowed")
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
if proxy.Proxy == "nginx" || !isBrowser {
c.JSON(401, gin.H{
"status": 401,
"message": "Unauthorized",
})
return
}
queries, err := query.Values(types.UnauthorizedQuery{
Username: userContext.Username,
Resource: strings.Split(host, ".")[0],
})
if err != nil {
log.Error().Err(err).Msg("Failed to build query")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
return
}
c.Header("Remote-User", userContext.Username)
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
})
return
}
log.Debug().Msg("Unauthorized")
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
if proxy.Proxy == "nginx" || !isBrowser {
c.JSON(401, gin.H{
"status": 401,
"message": "Unauthorized",
})
return
}
queries, err := query.Values(types.LoginQuery{
RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri),
})
if err != nil {
log.Error().Err(err).Msg("Failed to build query")
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
return
}
log.Debug().Interface("redirect_uri", fmt.Sprintf("%s://%s%s", proto, host, uri)).Msg("Redirecting to login")
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/?%s", h.Config.AppURL, queries.Encode()))
}

View File

@@ -1,15 +0,0 @@
package types
// API config is the configuration for the API
type APIConfig struct {
Port int
Address string
Secret string
AppURL string
CookieSecure bool
SessionExpiry int
DisableContinue bool
GenericName string
Title string
Domain string
}

View File

@@ -1,13 +0,0 @@
package types
// HealthCheckResponse is the response for the health check endpoint
type HealthCheckResponse struct {
Status int `json:"status" example:"200"`
Message string `json:"message" example:"Ok"`
}
// LogoutResponse is the response for the health check endpoint
type LogoutResponse struct {
Status int `json:"status" example:"200"`
Message string `json:"message" example:"Logged out"`
}

View File

@@ -55,7 +55,6 @@ type Config struct {
SessionExpiry int `mapstructure:"session-expiry"` SessionExpiry int `mapstructure:"session-expiry"`
LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"` LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"`
Title string `mapstructure:"app-title"` Title string `mapstructure:"app-title"`
EnvFile string `mapstructure:"env-file"`
} }
// UserContext is the context for the user // UserContext is the context for the user
@@ -67,6 +66,19 @@ type UserContext struct {
TotpPending bool TotpPending bool
} }
// APIConfig is the configuration for the API
type APIConfig struct {
Port int
Address string
Secret string
AppURL string
CookieSecure bool
SessionExpiry int
DisableContinue bool
GenericName string
Title string
}
// OAuthConfig is the configuration for the providers // OAuthConfig is the configuration for the providers
type OAuthConfig struct { type OAuthConfig struct {
GithubClientId string GithubClientId string
@@ -126,28 +138,22 @@ type Proxy struct {
Proxy string `uri:"proxy" binding:"required"` Proxy string `uri:"proxy" binding:"required"`
} }
// User Context response is the response for the user context endpoint // Status response
type UserContextResponse struct { type Status 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"` Status int `json:"status"`
Message string `json:"message"` Message string `json:"message"`
IsLoggedIn bool `json:"isLoggedIn"`
Username string `json:"username"`
Provider string `json:"provider"`
Oauth bool `json:"oauth"`
ConfiguredProviders []string `json:"configuredProviders"` ConfiguredProviders []string `json:"configuredProviders"`
DisableContinue bool `json:"disableContinue"` DisableContinue bool `json:"disableContinue"`
Title string `json:"title"` Title string `json:"title"`
GenericName string `json:"genericName"` GenericName string `json:"genericName"`
TotpPending bool `json:"totpPending"`
} }
// Totp request is the request for the totp endpoint // Totp request
type TotpRequest struct { type Totp struct {
Code string `json:"code"` Code string `json:"code"`
} }

View File

@@ -1,23 +0,0 @@
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"]

View File

@@ -1,42 +0,0 @@
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;
};

View File

@@ -17,7 +17,7 @@ export const UserContextProvider = ({
} = useQuery({ } = useQuery({
queryKey: ["userContext"], queryKey: ["userContext"],
queryFn: async () => { queryFn: async () => {
const res = await axios.get("/api/user"); const res = await axios.get("/api/status");
return res.data; return res.data;
}, },
}); });

View File

@@ -16,7 +16,6 @@ import { NotFoundPage } from "./pages/not-found-page.tsx";
import { UnauthorizedPage } from "./pages/unauthorized-page.tsx"; import { UnauthorizedPage } from "./pages/unauthorized-page.tsx";
import { InternalServerError } from "./pages/internal-server-error.tsx"; import { InternalServerError } from "./pages/internal-server-error.tsx";
import { TotpPage } from "./pages/totp-page.tsx"; import { TotpPage } from "./pages/totp-page.tsx";
import { AppContextProvider } from "./context/app-context.tsx";
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@@ -31,22 +30,20 @@ createRoot(document.getElementById("root")!).render(
<MantineProvider forceColorScheme="dark"> <MantineProvider forceColorScheme="dark">
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Notifications /> <Notifications />
<AppContextProvider> <UserContextProvider>
<UserContextProvider> <BrowserRouter>
<BrowserRouter> <Routes>
<Routes> <Route path="/" element={<App />} />
<Route path="/" element={<App />} /> <Route path="/login" element={<LoginPage />} />
<Route path="/login" element={<LoginPage />} /> <Route path="/totp" element={<TotpPage />} />
<Route path="/totp" element={<TotpPage />} /> <Route path="/logout" element={<LogoutPage />} />
<Route path="/logout" element={<LogoutPage />} /> <Route path="/continue" element={<ContinuePage />} />
<Route path="/continue" element={<ContinuePage />} /> <Route path="/unauthorized" element={<UnauthorizedPage />} />
<Route path="/unauthorized" element={<UnauthorizedPage />} /> <Route path="/error" element={<InternalServerError />} />
<Route path="/error" element={<InternalServerError />} /> <Route path="*" element={<NotFoundPage />} />
<Route path="*" element={<NotFoundPage />} /> </Routes>
</Routes> </BrowserRouter>
</BrowserRouter> </UserContextProvider>
</UserContextProvider>
</AppContextProvider>
</QueryClientProvider> </QueryClientProvider>
</MantineProvider> </MantineProvider>
</StrictMode>, </StrictMode>,

View File

@@ -5,15 +5,13 @@ import { useUserContext } from "../context/user-context";
import { Layout } from "../components/layouts/layout"; import { Layout } from "../components/layouts/layout";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { isQueryValid } from "../utils/utils"; import { isQueryValid } from "../utils/utils";
import { useAppContext } from "../context/app-context";
export const ContinuePage = () => { export const ContinuePage = () => {
const queryString = window.location.search; const queryString = window.location.search;
const params = new URLSearchParams(queryString); const params = new URLSearchParams(queryString);
const redirectUri = params.get("redirect_uri") ?? ""; const redirectUri = params.get("redirect_uri") ?? "";
const { isLoggedIn } = useUserContext(); const { isLoggedIn, disableContinue } = useUserContext();
const { disableContinue } = useAppContext();
if (!isLoggedIn) { if (!isLoggedIn) {
return <Navigate to={`/login?redirect_uri=${redirectUri}`} />; return <Navigate to={`/login?redirect_uri=${redirectUri}`} />;

View File

@@ -9,15 +9,14 @@ import { OAuthButtons } from "../components/auth/oauth-buttons";
import { LoginFormValues } from "../schemas/login-schema"; import { LoginFormValues } from "../schemas/login-schema";
import { LoginForm } from "../components/auth/login-forn"; import { LoginForm } from "../components/auth/login-forn";
import { isQueryValid } from "../utils/utils"; import { isQueryValid } from "../utils/utils";
import { useAppContext } from "../context/app-context";
export const LoginPage = () => { export const LoginPage = () => {
const queryString = window.location.search; const queryString = window.location.search;
const params = new URLSearchParams(queryString); const params = new URLSearchParams(queryString);
const redirectUri = params.get("redirect_uri") ?? ""; const redirectUri = params.get("redirect_uri") ?? "";
const { isLoggedIn } = useUserContext(); const { isLoggedIn, configuredProviders, title, genericName } =
const { configuredProviders, title, genericName } = useAppContext(); useUserContext();
const oauthProviders = configuredProviders.filter( const oauthProviders = configuredProviders.filter(
(value) => value !== "username", (value) => value !== "username",

View File

@@ -6,11 +6,9 @@ import { useUserContext } from "../context/user-context";
import { Navigate } from "react-router"; import { Navigate } from "react-router";
import { Layout } from "../components/layouts/layout"; import { Layout } from "../components/layouts/layout";
import { capitalize } from "../utils/utils"; import { capitalize } from "../utils/utils";
import { useAppContext } from "../context/app-context";
export const LogoutPage = () => { export const LogoutPage = () => {
const { isLoggedIn, username, oauth, provider } = useUserContext(); const { isLoggedIn, username, oauth, provider, genericName } = useUserContext();
const { genericName } = useAppContext();
if (!isLoggedIn) { if (!isLoggedIn) {
return <Navigate to="/login" />; return <Navigate to="/login" />;
@@ -47,9 +45,8 @@ export const LogoutPage = () => {
</Text> </Text>
<Text> <Text>
You are currently logged in as <Code>{username}</Code> You are currently logged in as <Code>{username}</Code>
{oauth && {oauth && ` using ${capitalize(provider === "generic" ? genericName : provider)} OAuth`}. Click the button
` using ${capitalize(provider === "generic" ? genericName : provider)} OAuth`} below to log out.
. Click the button below to log out.
</Text> </Text>
<Button <Button
fullWidth fullWidth

View File

@@ -6,15 +6,13 @@ import { TotpForm } from "../components/auth/totp-form";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { useAppContext } from "../context/app-context";
export const TotpPage = () => { export const TotpPage = () => {
const queryString = window.location.search; const queryString = window.location.search;
const params = new URLSearchParams(queryString); const params = new URLSearchParams(queryString);
const redirectUri = params.get("redirect_uri") ?? ""; const redirectUri = params.get("redirect_uri") ?? "";
const { totpPending, isLoggedIn } = useUserContext(); const { totpPending, isLoggedIn, title } = useUserContext();
const { title } = useAppContext();
if (isLoggedIn) { if (isLoggedIn) {
return <Navigate to={`/logout`} />; return <Navigate to={`/logout`} />;

View File

@@ -1,10 +0,0 @@
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>;

View File

@@ -5,6 +5,10 @@ export const userContextSchema = z.object({
username: z.string(), username: z.string(),
oauth: z.boolean(), oauth: z.boolean(),
provider: z.string(), provider: z.string(),
configuredProviders: z.array(z.string()),
disableContinue: z.boolean(),
title: z.string(),
genericName: z.string(),
totpPending: z.boolean(), totpPending: z.boolean(),
}); });

View File

@@ -4,14 +4,4 @@ import react from "@vitejs/plugin-react-swc";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: {
host: "0.0.0.0",
proxy: {
"/api": {
target: "http://tinyauth-backend:3000/api",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
}
}
}); });