mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 04:35:40 +00:00
Initial Commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# dist
|
||||||
|
internal/assets/dist
|
||||||
49
Dockerfile
Normal file
49
Dockerfile
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Site builder
|
||||||
|
FROM oven/bun:1.1.45-alpine AS site-builder
|
||||||
|
|
||||||
|
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 ./
|
||||||
|
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
# Builder
|
||||||
|
FROM golang:1.23-alpine3.21 AS builder
|
||||||
|
|
||||||
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
|
COPY go.mod ./
|
||||||
|
COPY go.sum ./
|
||||||
|
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY ./main.go ./
|
||||||
|
COPY ./cmd ./cmd
|
||||||
|
COPY ./internal ./internal
|
||||||
|
COPY --from=site-builder /site/dist ./internal/assets/dist
|
||||||
|
|
||||||
|
RUN go build
|
||||||
|
|
||||||
|
# Runner
|
||||||
|
FROM busybox:1.37-musl AS runner
|
||||||
|
|
||||||
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
|
COPY --from=builder /tinyauth/tinyauth ./
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["./tinyauth"]
|
||||||
24
cmd/root.go
Normal file
24
cmd/root.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"tinyauth/internal/api"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "tinyauth",
|
||||||
|
Short: "A dead simple login page for your apps.",
|
||||||
|
Long: `Tinyauth is an extremely simple traefik forward-auth login screen that makes securing your apps easy.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
api.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
36
docker-compose.yml
Normal file
36
docker-compose.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
container_name: traefik
|
||||||
|
image: traefik:v3.3
|
||||||
|
command: --api.insecure=true --providers.docker
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
labels:
|
||||||
|
traefik.http.middlewares.basic-auth.basicauth.users: user:$$2y$$05$$HgkRucUeFKgZ7ZsPkIJY6uAX2Nh8ZAeIlJ5Rpq.05yYBPsITTnnLu
|
||||||
|
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
container_name: nginx
|
||||||
|
image: nginx:latest
|
||||||
|
ports:
|
||||||
|
- 8000:80
|
||||||
|
labels:
|
||||||
|
traefik.enable: true
|
||||||
|
traefik.http.routers.nginx.rule: Host(`nginx.dev.local`)
|
||||||
|
traefik.http.services.nginx.loadbalancer.server.port: 80
|
||||||
|
traefik.http.routers.nginx.middlewares: tinyauth
|
||||||
|
|
||||||
|
tinyauth:
|
||||||
|
container_name: tinyauth
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
labels:
|
||||||
|
traefik.enable: true
|
||||||
|
traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`)
|
||||||
|
traefik.http.services.tinyauth.loadbalancer.server.port: 3000
|
||||||
43
go.mod
Normal file
43
go.mod
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
module tinyauth
|
||||||
|
|
||||||
|
go 1.23.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.10.0
|
||||||
|
github.com/spf13/cobra v1.8.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.12.7 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/gin-contrib/sessions v1.0.2 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.4 // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
|
github.com/gorilla/sessions v1.2.2 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
golang.org/x/arch v0.13.0 // indirect
|
||||||
|
golang.org/x/crypto v0.32.0 // indirect
|
||||||
|
golang.org/x/net v0.34.0 // indirect
|
||||||
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.3 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
105
go.sum
Normal file
105
go.sum
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=
|
||||||
|
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.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||||
|
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
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/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
|
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
|
||||||
|
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
|
||||||
|
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||||
|
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||||
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
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/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/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/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
||||||
|
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||||
|
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||||
|
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
|
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||||
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||||
|
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
|
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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
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/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
||||||
|
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||||
|
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
122
internal/api/api.go
Normal file
122
internal/api/api.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"tinyauth/internal/assets"
|
||||||
|
"tinyauth/internal/types"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run() {
|
||||||
|
router := gin.Default()
|
||||||
|
dist, _ := fs.Sub(assets.Assets, "dist")
|
||||||
|
fileServer := http.FileServer(http.FS(dist))
|
||||||
|
store := cookie.NewStore([]byte("secret"))
|
||||||
|
store.Options(sessions.Options{
|
||||||
|
Domain: ".dev.local",
|
||||||
|
Path: "/",
|
||||||
|
})
|
||||||
|
router.Use(sessions.Sessions("tinyauth", store))
|
||||||
|
|
||||||
|
router.Use(func(c *gin.Context) {
|
||||||
|
if !strings.HasPrefix(c.Request.URL.Path, "/api") {
|
||||||
|
_, err := fs.Stat(dist, strings.TrimPrefix(c.Request.URL.Path, "/"))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
c.Request.URL.Path = "/"
|
||||||
|
}
|
||||||
|
fileServer.ServeHTTP(c.Writer, c.Request)
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET("/api/auth", func (c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
value := session.Get("tinyauth")
|
||||||
|
|
||||||
|
if value == nil || value != "true" {
|
||||||
|
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
||||||
|
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
||||||
|
host := c.Request.Header.Get("X-Forwarded-Host")
|
||||||
|
queries := types.LoginQuery{
|
||||||
|
RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri),
|
||||||
|
}
|
||||||
|
values, _ := query.Values(queries)
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("http://tinyauth.dev.local?%s", values.Encode()))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Authorized",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/login", func (c *gin.Context) {
|
||||||
|
var login types.LoginRequest
|
||||||
|
|
||||||
|
err := c.BindJSON(&login)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"status": 400,
|
||||||
|
"message": "Bad Request",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if login.Email != "user@example.com" || login.Password != "password" {
|
||||||
|
c.JSON(401, gin.H{
|
||||||
|
"status": 401,
|
||||||
|
"message": "Unauthorized",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session := sessions.Default(c)
|
||||||
|
session.Set("tinyauth", "true")
|
||||||
|
session.Save()
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Logged in",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/logout", func (c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
session.Delete("tinyauth")
|
||||||
|
session.Save()
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Logged out",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET("/api/status", func (c *gin.Context) {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
value := session.Get("tinyauth")
|
||||||
|
|
||||||
|
if value == nil || value != "true" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"isLoggedIn": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"status": 200,
|
||||||
|
"isLoggedIn": true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.Run(":3000")
|
||||||
|
}
|
||||||
8
internal/assets/assets.go
Normal file
8
internal/assets/assets.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package assets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed dist
|
||||||
|
var Assets embed.FS
|
||||||
10
internal/types/types.go
Normal file
10
internal/types/types.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type LoginQuery struct {
|
||||||
|
RedirectURI string `url:"redirect_uri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
7
main.go
Normal file
7
main.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "tinyauth/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
||||||
24
site/.gitignore
vendored
Normal file
24
site/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
3
site/.prettierignore
Normal file
3
site/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Ignore artifacts:
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
1
site/.prettierrc
Normal file
1
site/.prettierrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
BIN
site/bun.lockb
Executable file
BIN
site/bun.lockb
Executable file
Binary file not shown.
28
site/eslint.config.js
Normal file
28
site/eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import js from "@eslint/js";
|
||||||
|
import globals from "globals";
|
||||||
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ["dist"] },
|
||||||
|
{
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
files: ["**/*.{ts,tsx}"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
"react-hooks": reactHooks,
|
||||||
|
"react-refresh": reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
"react-refresh/only-export-components": [
|
||||||
|
"warn",
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
13
site/index.html
Normal file
13
site/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Tinyauth</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2249
site/package-lock.json
generated
Normal file
2249
site/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
site/package.json
Normal file
41
site/package.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "site",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mantine/core": "^7.16.0",
|
||||||
|
"@mantine/form": "^7.16.0",
|
||||||
|
"@mantine/hooks": "^7.16.0",
|
||||||
|
"@mantine/notifications": "^7.16.0",
|
||||||
|
"@tanstack/react-query": "4",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router": "^7.1.3",
|
||||||
|
"zod": "^3.24.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.17.0",
|
||||||
|
"@types/react": "^18.3.18",
|
||||||
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.16",
|
||||||
|
"globals": "^15.14.0",
|
||||||
|
"postcss": "^8.5.1",
|
||||||
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
|
"postcss-simple-vars": "^7.0.1",
|
||||||
|
"prettier": "3.4.2",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"typescript-eslint": "^8.18.2",
|
||||||
|
"vite": "^6.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
site/postcss.config.cjs
Normal file
14
site/postcss.config.cjs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
"postcss-preset-mantine": {},
|
||||||
|
"postcss-simple-vars": {
|
||||||
|
variables: {
|
||||||
|
"mantine-breakpoint-xs": "36em",
|
||||||
|
"mantine-breakpoint-sm": "48em",
|
||||||
|
"mantine-breakpoint-md": "62em",
|
||||||
|
"mantine-breakpoint-lg": "75em",
|
||||||
|
"mantine-breakpoint-xl": "88em",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
0
site/public/.gitkeep
Normal file
0
site/public/.gitkeep
Normal file
17
site/src/App.tsx
Normal file
17
site/src/App.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Navigate } from "react-router";
|
||||||
|
import { useUserContext } from "./context/user-context";
|
||||||
|
import { LogoutPage } from "./pages/logout-page";
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const params = new URLSearchParams(queryString);
|
||||||
|
const redirectUri = params.get("redirect_uri");
|
||||||
|
|
||||||
|
const { isLoggedIn } = useUserContext();
|
||||||
|
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return <Navigate to={`/login?redirect_uri=${redirectUri}`} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <LogoutPage />;
|
||||||
|
};
|
||||||
12
site/src/components/layouts/layout.tsx
Normal file
12
site/src/components/layouts/layout.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Center, Flex } from "@mantine/core";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
export const Layout = ({ children }: { children: ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<Center style={{ minHeight: "100vh" }}>
|
||||||
|
<Flex direction="column" flex="1" maw={350}>
|
||||||
|
{children}
|
||||||
|
</Flex>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
};
|
||||||
42
site/src/context/user-context.tsx
Normal file
42
site/src/context/user-context.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import React, { createContext, useContext } from "react";
|
||||||
|
import { UserContextSchemaType } from "../schemas/user-context-schema";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const UserContext = createContext<UserContextSchemaType | null>(null);
|
||||||
|
|
||||||
|
export const UserContextProvider = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
data: userContext,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ["isLoggedIn"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await axios.get("/api/status");
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error && !isLoading) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={userContext}>{children}</UserContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUserContext = () => {
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
|
||||||
|
if (context === null) {
|
||||||
|
throw new Error("useUserContext must be used within a UserContextProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
44
site/src/main.tsx
Normal file
44
site/src/main.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { App } from "./App.tsx";
|
||||||
|
import { MantineProvider } from "@mantine/core";
|
||||||
|
import { Notifications } from "@mantine/notifications";
|
||||||
|
import "@mantine/core/styles.css";
|
||||||
|
import "@mantine/notifications/styles.css";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { BrowserRouter, Route } from "react-router";
|
||||||
|
import { Routes } from "react-router";
|
||||||
|
import { UserContextProvider } from "./context/user-context.tsx";
|
||||||
|
import { LoginPage } from "./pages/login-page.tsx";
|
||||||
|
import { LogoutPage } from "./pages/logout-page.tsx";
|
||||||
|
import { ContinuePage } from "./pages/continue-page.tsx";
|
||||||
|
import { NotFoundPage } from "./pages/not-found-page.tsx";
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
suspense: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<MantineProvider forceColorScheme="dark">
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<Notifications />
|
||||||
|
<UserContextProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<App />} />
|
||||||
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
<Route path="/logout" element={<LogoutPage />} />
|
||||||
|
<Route path="/continue" element={<ContinuePage />} />
|
||||||
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserContextProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</MantineProvider>
|
||||||
|
</StrictMode>,
|
||||||
|
);
|
||||||
53
site/src/pages/continue-page.tsx
Normal file
53
site/src/pages/continue-page.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { Button, Paper, Text } from "@mantine/core";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { Navigate } from "react-router";
|
||||||
|
import { useUserContext } from "../context/user-context";
|
||||||
|
import { Layout } from "../components/layouts/layout";
|
||||||
|
|
||||||
|
export const ContinuePage = () => {
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const params = new URLSearchParams(queryString);
|
||||||
|
const redirectUri = params.get("redirect_uri");
|
||||||
|
|
||||||
|
const { isLoggedIn } = useUserContext();
|
||||||
|
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return <Navigate to="/login" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirect = () => {
|
||||||
|
notifications.show({
|
||||||
|
title: "Redirecting",
|
||||||
|
message: "You should be redirected to the app soon",
|
||||||
|
color: "blue",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.replace(redirectUri!);
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||||
|
{redirectUri ? (
|
||||||
|
<>
|
||||||
|
<Text size="xl" fw={700}>
|
||||||
|
Continue
|
||||||
|
</Text>
|
||||||
|
<Text>Click the button to continue to your app.</Text>
|
||||||
|
<Button fullWidth mt="xl" onClick={redirect}>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text size="xl" fw={700}>
|
||||||
|
Logged in
|
||||||
|
</Text>
|
||||||
|
<Text>You are now signed in and can use your apps.</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
99
site/src/pages/login-page.tsx
Normal file
99
site/src/pages/login-page.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { Button, Paper, PasswordInput, TextInput, Title } from "@mantine/core";
|
||||||
|
import { useForm, zodResolver } from "@mantine/form";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useUserContext } from "../context/user-context";
|
||||||
|
import { Navigate } from "react-router";
|
||||||
|
import { Layout } from "../components/layouts/layout";
|
||||||
|
|
||||||
|
export const LoginPage = () => {
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const params = new URLSearchParams(queryString);
|
||||||
|
const redirectUri = params.get("redirect_uri");
|
||||||
|
|
||||||
|
const { isLoggedIn } = useUserContext();
|
||||||
|
|
||||||
|
if (isLoggedIn) {
|
||||||
|
return <Navigate to="/logout" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
email: z.string().email({ message: "Invalid email" }),
|
||||||
|
password: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
mode: "uncontrolled",
|
||||||
|
initialValues: {
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
|
validate: zodResolver(schema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const loginMutation = useMutation({
|
||||||
|
mutationFn: (login: FormValues) => {
|
||||||
|
return axios.post("/api/login", login);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
notifications.show({
|
||||||
|
title: "Failed to login",
|
||||||
|
message: "Check your email and password",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
notifications.show({
|
||||||
|
title: "Logged in",
|
||||||
|
message: "Welcome back!",
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.replace(`/continue?redirect_uri=${redirectUri}`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = (values: FormValues) => {
|
||||||
|
loginMutation.mutate(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Title ta="center">Welcome back!</Title>
|
||||||
|
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||||
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
|
<TextInput
|
||||||
|
label="Email"
|
||||||
|
placeholder="you@example.com"
|
||||||
|
required
|
||||||
|
disabled={loginMutation.isLoading}
|
||||||
|
key={form.key("email")}
|
||||||
|
{...form.getInputProps("email")}
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
label="Password"
|
||||||
|
placeholder="password"
|
||||||
|
required
|
||||||
|
mt="md"
|
||||||
|
disabled={loginMutation.isLoading}
|
||||||
|
key={form.key("password")}
|
||||||
|
{...form.getInputProps("password")}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
mt="xl"
|
||||||
|
type="submit"
|
||||||
|
loading={loginMutation.isLoading}
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Paper>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
59
site/src/pages/logout-page.tsx
Normal file
59
site/src/pages/logout-page.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Button, Paper, Text } from "@mantine/core";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useUserContext } from "../context/user-context";
|
||||||
|
import { Navigate } from "react-router";
|
||||||
|
import { Layout } from "../components/layouts/layout";
|
||||||
|
|
||||||
|
export const LogoutPage = () => {
|
||||||
|
const { isLoggedIn } = useUserContext();
|
||||||
|
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return <Navigate to="/login" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logoutMutation = useMutation({
|
||||||
|
mutationFn: () => {
|
||||||
|
return axios.post("/api/logout");
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
notifications.show({
|
||||||
|
title: "Failed to logout",
|
||||||
|
message: "Please try again",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
notifications.show({
|
||||||
|
title: "Logged out",
|
||||||
|
message: "Goodbye!",
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||||
|
<Text size="xl" fw={700}>
|
||||||
|
Logout
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
You are already logged in, click the button below to log out.
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
mt="xl"
|
||||||
|
onClick={() => logoutMutation.mutate()}
|
||||||
|
loading={logoutMutation.isLoading}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
</Paper>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
18
site/src/pages/not-found-page.tsx
Normal file
18
site/src/pages/not-found-page.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Button, Paper, Text } from "@mantine/core";
|
||||||
|
import { Layout } from "../components/layouts/layout";
|
||||||
|
|
||||||
|
export const NotFoundPage = () => {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
|
||||||
|
<Text size="xl" fw={700}>
|
||||||
|
Not found
|
||||||
|
</Text>
|
||||||
|
<Text>The page you are looking for does not exist.</Text>
|
||||||
|
<Button fullWidth mt="xl" onClick={() => window.location.replace("/")}>
|
||||||
|
Go home
|
||||||
|
</Button>
|
||||||
|
</Paper>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
7
site/src/schemas/user-context-schema.ts
Normal file
7
site/src/schemas/user-context-schema.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const userContextSchema = z.object({
|
||||||
|
isLoggedIn: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UserContextSchemaType = z.infer<typeof userContextSchema>;
|
||||||
1
site/src/vite-env.d.ts
vendored
Normal file
1
site/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
26
site/tsconfig.app.json
Normal file
26
site/tsconfig.app.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
site/tsconfig.json
Normal file
7
site/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
24
site/tsconfig.node.json
Normal file
24
site/tsconfig.node.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
7
site/vite.config.ts
Normal file
7
site/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react-swc";
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user