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
10 changed files with 168 additions and 156 deletions

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={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{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 ' *)

View File

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

View File

@@ -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

@@ -8,12 +8,12 @@ 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.dev.local`) 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
@@ -31,4 +31,4 @@ services:
traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`) traefik.http.routers.tinyauth.rule: Host(`tinyauth.dev.local`)
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: Remote-User traefik.http.middlewares.tinyauth.forwardauth.authResponseHeaders: X-Tinyauth-User

View File

@@ -29,4 +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: Remote-User traefik.http.middlewares.tinyauth.forwardauth.authResponseHeaders: X-Tinyauth-User

View File

@@ -131,24 +131,18 @@ func (api *API) SetupRoutes() {
return return
} }
// Check if the request is coming from a browser (tools like curl/bruno use */* and they don't include the text/html)
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") log.Debug().Interface("proxy", proxy.Proxy).Msg("Got proxy")
// Check if using basic auth
_, _, basicAuth := c.Request.BasicAuth()
// Check if auth is enabled // Check if auth is enabled
authEnabled, authEnabledErr := api.Auth.AuthEnabled(c) authEnabled, authEnabledErr := api.Auth.AuthEnabled(c)
// Handle error // Handle error
if authEnabledErr != nil { if authEnabledErr != nil {
// Return 500 if nginx is the proxy or if the request is not coming from a browser // Return 500 if nginx is the proxy or if the request is using basic auth
if proxy.Proxy == "nginx" || !isBrowser { if proxy.Proxy == "nginx" || basicAuth {
log.Error().Err(authEnabledErr).Msg("Failed to check if auth is enabled") log.Error().Err(authEnabledErr).Msg("Failed to check if auth is enabled")
c.JSON(500, gin.H{ c.JSON(500, gin.H{
"status": 500, "status": 500,
@@ -192,8 +186,8 @@ func (api *API) SetupRoutes() {
// Check if there was an error // Check if there was an error
if appAllowedErr != nil { if appAllowedErr != nil {
// Return 500 if nginx is the proxy or if the request is not coming from a browser // Return 500 if nginx is the proxy or if the request is using basic auth
if proxy.Proxy == "nginx" || !isBrowser { if proxy.Proxy == "nginx" || basicAuth {
log.Error().Err(appAllowedErr).Msg("Failed to check if app is allowed") log.Error().Err(appAllowedErr).Msg("Failed to check if app is allowed")
c.JSON(500, gin.H{ c.JSON(500, gin.H{
"status": 500, "status": 500,
@@ -214,11 +208,9 @@ func (api *API) SetupRoutes() {
if !appAllowed { if !appAllowed {
log.Warn().Str("username", userContext.Username).Str("host", host).Msg("User not allowed") log.Warn().Str("username", userContext.Username).Str("host", host).Msg("User not allowed")
// Set WWW-Authenticate header // Return 401 if nginx is the proxy or if the request is using an Authorization header
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"") if proxy.Proxy == "nginx" || basicAuth {
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
// Return 401 if nginx is the proxy or if the request is not coming from a browser
if proxy.Proxy == "nginx" || !isBrowser {
c.JSON(401, gin.H{ c.JSON(401, gin.H{
"status": 401, "status": 401,
"message": "Unauthorized", "message": "Unauthorized",
@@ -245,7 +237,7 @@ func (api *API) SetupRoutes() {
} }
// Set the user header // Set the user header
c.Header("Remote-User", userContext.Username) c.Header("X-Tinyauth-User", userContext.Username)
// The user is allowed to access the app // The user is allowed to access the app
c.JSON(200, gin.H{ c.JSON(200, gin.H{
@@ -260,11 +252,9 @@ func (api *API) SetupRoutes() {
// The user is not logged in // The user is not logged in
log.Debug().Msg("Unauthorized") log.Debug().Msg("Unauthorized")
// Set www-authenticate header // Return 401 if nginx is the proxy or if the request is using an Authorization header
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"") if proxy.Proxy == "nginx" || basicAuth {
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
// Return 401 if nginx is the proxy or if the request is not coming from a browser
if proxy.Proxy == "nginx" || !isBrowser {
c.JSON(401, gin.H{ c.JSON(401, gin.H{
"status": 401, "status": 401,
"message": "Unauthorized", "message": "Unauthorized",

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