mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-04-28 08:28:12 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a736c4ad90 | |||
| 74901dd88f | |||
| 5d95123dcb | |||
| c364b8682c | |||
| ab7c81f63b | |||
| a9a782a9e4 | |||
| 399dee2ee5 | |||
| 6422d5e491 | |||
| a96ee13876 | |||
| 92b435d8cb | |||
| 03164f6c97 | |||
| f3186571cc | |||
| 3906e50925 | |||
| ff81f91366 | |||
| 479f165781 | |||
| 36c7872a94 |
@@ -3,7 +3,8 @@ name: Bug report
|
||||
about: Create a report to help improve Tinyauth
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: steveiliop56
|
||||
assignees:
|
||||
- steveiliop56
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: enhancement
|
||||
assignees: steveiliop56
|
||||
assignees:
|
||||
- steveiliop56
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Delete old release
|
||||
run: gh release delete --cleanup-tag --yes nightly || echo release not found
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
- generate-metadata
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
|
||||
@@ -80,12 +80,12 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -ldflags "-s -w -X github.com/steveiliop56/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
||||
go build -ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: tinyauth-amd64
|
||||
path: tinyauth-amd64
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
- generate-metadata
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
|
||||
@@ -126,12 +126,12 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -ldflags "-s -w -X github.com/steveiliop56/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
||||
go build -ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: tinyauth-arm64
|
||||
path: tinyauth-arm64
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
- generate-metadata
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: digests-linux-amd64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
- image-build
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
|
||||
@@ -245,7 +245,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: digests-distroless-linux-amd64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -259,7 +259,7 @@ jobs:
|
||||
- generate-metadata
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
|
||||
@@ -302,7 +302,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: digests-linux-arm64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -317,7 +317,7 @@ jobs:
|
||||
- image-build-arm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
|
||||
@@ -361,7 +361,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: digests-distroless-linux-arm64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Generate metadata
|
||||
id: metadata
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- generate-metadata
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
@@ -56,12 +56,12 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -ldflags "-s -w -X github.com/steveiliop56/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
||||
go build -ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: tinyauth-amd64
|
||||
path: tinyauth-amd64
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- generate-metadata
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
@@ -99,12 +99,12 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
cp -r frontend/dist internal/assets/dist
|
||||
go build -ldflags "-s -w -X github.com/steveiliop56/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
||||
go build -ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/config.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/config.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/config.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: tinyauth-arm64
|
||||
path: tinyauth-arm64
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
- generate-metadata
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: digests-linux-amd64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
- image-build
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: digests-distroless-linux-amd64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
- generate-metadata
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
@@ -266,7 +266,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: digests-linux-arm64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -280,7 +280,7 @@ jobs:
|
||||
- image-build-arm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
@@ -322,7 +322,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
name: digests-distroless-linux-arm64
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
branch_protection_rule:
|
||||
schedule:
|
||||
- cron: "31 17 * * 5"
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
|
||||
permissions:
|
||||
security-events: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run analysis
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
publish_results: true
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
- name: Upload to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Generate Sponsors
|
||||
uses: JamesIves/github-sponsors-readme-action@v1
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ Contributing to Tinyauth is straightforward. Follow the steps below to set up a
|
||||
Start by cloning the repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/steveiliop56/tinyauth
|
||||
git clone https://github.com/tinyauthapp/tinyauth
|
||||
cd tinyauth
|
||||
```
|
||||
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
# Site builder
|
||||
FROM oven/bun:1.3.12-alpine AS frontend-builder
|
||||
FROM oven/bun:1.3.13-alpine AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
@@ -38,9 +38,9 @@ COPY ./internal ./internal
|
||||
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||
|
||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w \
|
||||
-X github.com/steveiliop56/tinyauth/internal/config.Version=${VERSION} \
|
||||
-X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||
-X github.com/tinyauthapp/tinyauth/internal/config.Version=${VERSION} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/config.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||
|
||||
# Runner
|
||||
FROM alpine:3.23 AS runner
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Site builder
|
||||
FROM oven/bun:1.3.12-alpine AS frontend-builder
|
||||
FROM oven/bun:1.3.13-alpine AS frontend-builder
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
@@ -40,9 +40,9 @@ COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
|
||||
RUN mkdir -p data
|
||||
|
||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w \
|
||||
-X github.com/steveiliop56/tinyauth/internal/config.Version=${VERSION} \
|
||||
-X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||
-X github.com/tinyauthapp/tinyauth/internal/config.Version=${VERSION} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/config.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth
|
||||
|
||||
# Runner
|
||||
FROM gcr.io/distroless/static-debian12:latest AS runner
|
||||
|
||||
@@ -37,9 +37,9 @@ webui: clean-webui
|
||||
# Build the binary
|
||||
binary: webui
|
||||
CGO_ENABLED=$(CGO_ENABLED) go build -ldflags "-s -w \
|
||||
-X github.com/steveiliop56/tinyauth/internal/config.Version=${TAG_NAME} \
|
||||
-X github.com/steveiliop56/tinyauth/internal/config.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/steveiliop56/tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/config.Version=${TAG_NAME} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/config.CommitHash=${COMMIT_HASH} \
|
||||
-X github.com/tinyauthapp/tinyauth/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}" \
|
||||
-o ${BIN_NAME} ./cmd/tinyauth
|
||||
|
||||
# Build for amd64
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img alt="License" src="https://img.shields.io/github/license/steveiliop56/tinyauth">
|
||||
<img alt="Release" src="https://img.shields.io/github/v/release/steveiliop56/tinyauth">
|
||||
<img alt="Issues" src="https://img.shields.io/github/issues/steveiliop56/tinyauth">
|
||||
<img alt="Tinyauth CI" src="https://github.com/steveiliop56/tinyauth/actions/workflows/ci.yml/badge.svg">
|
||||
<img alt="License" src="https://img.shields.io/github/license/tinyauthapp/tinyauth">
|
||||
<img alt="Release" src="https://img.shields.io/github/v/release/tinyauthapp/tinyauth">
|
||||
<img alt="Issues" src="https://img.shields.io/github/issues/tinyauthapp/tinyauth">
|
||||
<img alt="Tinyauth CI" src="https://github.com/tinyauthapp/tinyauth/actions/workflows/ci.yml/badge.svg">
|
||||
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/tinyauth"><img src="https://badges.crowdin.net/tinyauth/localized.svg"></a>
|
||||
<a href="https://scorecard.dev/viewer/?uri=github.com/tinyauthapp/tinyauth" target="_blank" title="OpenSSF Scorecard">
|
||||
<img src="https://api.scorecard.dev/projects/github.com/tinyauthapp/tinyauth/badge">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
@@ -39,7 +42,7 @@ If you are still not sure if Tinyauth suits your needs you can try out the [demo
|
||||
|
||||
You can find documentation and guides on all of the available configuration of Tinyauth in the [website](https://tinyauth.app).
|
||||
|
||||
If you wish to contribute to the documentation head over to the [repository](https://github.com/steveiliop56/tinyauth-docs).
|
||||
If you wish to contribute to the documentation head over to the [repository](https://github.com/tinyauthapp/docs).
|
||||
|
||||
## Discord
|
||||
|
||||
@@ -47,7 +50,7 @@ Tinyauth has a [Discord](https://discord.gg/eHzVaCzRRd) server. Feel free to hop
|
||||
|
||||
## Contributing
|
||||
|
||||
All contributions to the codebase are welcome! If you have any free time, feel free to pick up an [issue](https://github.com/steveiliop56/tinyauth/issues) or add your own missing features. Make sure to check out the [contributing guide](./CONTRIBUTING.md) for instructions on how to get the development server up and running.
|
||||
All contributions to the codebase are welcome! If you have any free time, feel free to pick up an [issue](https://github.com/tinyauthapp/tinyauth/issues) or add your own missing features. Make sure to check out the [contributing guide](./CONTRIBUTING.md) for instructions on how to get the development server up and running.
|
||||
|
||||
## Localization
|
||||
|
||||
@@ -72,4 +75,4 @@ A big thank you to the following people for providing me with more coffee:
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#steveiliop56/tinyauth&Date)
|
||||
[](https://www.star-history.com/#tinyauthapp/tinyauth&Date)
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
It is recommended to use the [latest](https://github.com/steveiliop56/tinyauth/releases/latest) available version of tinyauth. This is because it includes security fixes, new features and dependency updates. Older versions, especially major ones, are not supported and won't receive security or patch updates.
|
||||
It is recommended to use the [latest](https://github.com/tinyauthapp/tinyauth/releases/latest) available version of tinyauth. This is because it includes security fixes, new features and dependency updates. Older versions, especially major ones, are not supported and won't receive security or patch updates.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"embeds": [
|
||||
{
|
||||
"title": "Welcome to Tinyauth Discord!",
|
||||
"description": "Tinyauth is a simple authentication middleware that adds a simple login screen or OAuth with Google, Github and any provider to all of your docker apps. It supports all the popular proxies like Traefik, Nginx and Caddy.\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 a simple login screen or OAuth with Google, Github and any provider to all of your docker apps. It supports all the popular proxies like Traefik, Nginx and Caddy.\n\n**Information**\n\n• Github: <https://github.com/tinyauthapp/tinyauth>\n• Website: <https://tinyauth.app>",
|
||||
"url": "https://tinyauth.app",
|
||||
"color": 7002085,
|
||||
"author": {
|
||||
@@ -14,9 +14,9 @@
|
||||
},
|
||||
"timestamp": "2025-06-06T12:25:27.629Z",
|
||||
"thumbnail": {
|
||||
"url": "https://github.com/steveiliop56/tinyauth/blob/main/assets/logo.png?raw=true"
|
||||
"url": "https://github.com/tinyauthapp/tinyauth/blob/main/assets/logo.png?raw=true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"attachments": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/mdp/qrterminal/v3"
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"fmt"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/steveiliop56/tinyauth/internal/bootstrap"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/loaders"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/loaders"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
traefik.http.routers.whoami.middlewares: tinyauth
|
||||
|
||||
tinyauth:
|
||||
image: ghcr.io/steveiliop56/tinyauth:v5
|
||||
image: ghcr.io/tinyauthapp/tinyauth:v5
|
||||
environment:
|
||||
- TINYAUTH_APPURL=https://tinyauth.example.com
|
||||
- TINYAUTH_AUTH_USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password
|
||||
|
||||
@@ -17,6 +17,7 @@ interface Props {
|
||||
onSubmit: (data: LoginSchema) => void;
|
||||
loading?: boolean;
|
||||
formId?: string;
|
||||
params?: string;
|
||||
}
|
||||
|
||||
export const LoginForm = (props: Props) => {
|
||||
@@ -71,6 +72,12 @@ export const LoginForm = (props: Props) => {
|
||||
</FormControl>
|
||||
<a
|
||||
href="/forgot-password"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.location.replace(
|
||||
`/forgot-password${props.params ? `${props.params}` : ""}`,
|
||||
);
|
||||
}}
|
||||
className="text-muted-foreground hover:text-muted-foreground/80 text-sm absolute right-0 bottom-[2.565rem]" // 2.565 is *just* perfect
|
||||
>
|
||||
{t("forgotPasswordTitle")}
|
||||
|
||||
@@ -79,5 +79,6 @@
|
||||
"profileScopeName": "Profile",
|
||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||
"groupsScopeName": "Groups",
|
||||
"groupsScopeDescription": "Allows the app to access your group information."
|
||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
||||
"backToLoginButton": "Back to login"
|
||||
}
|
||||
|
||||
@@ -79,5 +79,10 @@
|
||||
"profileScopeName": "Profile",
|
||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||
"groupsScopeName": "Groups",
|
||||
"groupsScopeDescription": "Allows the app to access your group information."
|
||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
||||
"backToLoginButton": "Back to login",
|
||||
"phoneScopeName": "Phone",
|
||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
||||
"addressScopeName": "Address",
|
||||
"addressScopeDescription": "Allows the app to access your address."
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { toast } from "sonner";
|
||||
import { useOIDCParams } from "@/lib/hooks/oidc";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TFunction } from "i18next";
|
||||
import { Mail, Shield, User, Users } from "lucide-react";
|
||||
import { Mail, MapPin, Phone, Shield, User, Users } from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -61,6 +61,18 @@ const createScopeMap = (t: TFunction<"translation", undefined>): Scope[] => {
|
||||
description: t("groupsScopeDescription"),
|
||||
icon: <Users {...scopeMapIconProps} />,
|
||||
},
|
||||
{
|
||||
id: "phone",
|
||||
name: t("phoneScopeName"),
|
||||
description: t("phoneScopeDescription"),
|
||||
icon: <Phone {...scopeMapIconProps} />,
|
||||
},
|
||||
{
|
||||
id: "address",
|
||||
name: t("addressScopeName"),
|
||||
description: t("addressScopeDescription"),
|
||||
icon: <MapPin {...scopeMapIconProps} />,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -10,12 +10,13 @@ import { Button } from "@/components/ui/button";
|
||||
import { useAppContext } from "@/context/app-context";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Markdown from "react-markdown";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useLocation } from "react-router";
|
||||
|
||||
export const ForgotPasswordPage = () => {
|
||||
const { forgotPasswordMessage } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { search } = useLocation();
|
||||
const searchParams = new URLSearchParams(search);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@@ -36,10 +37,13 @@ export const ForgotPasswordPage = () => {
|
||||
className="w-full"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
navigate("/login");
|
||||
const eparams = searchParams.toString();
|
||||
window.location.replace(
|
||||
`/login${eparams.length > 0 ? `?${eparams}` : ""}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t("notFoundButton")}
|
||||
{t("backToLoginButton")}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
@@ -264,6 +264,10 @@ export const LoginPage = () => {
|
||||
onSubmit={(values) => loginMutate(values)}
|
||||
loading={loginIsPending || oauthIsPending}
|
||||
formId={formId}
|
||||
params={(() => {
|
||||
const eparams = searchParams.toString();
|
||||
return eparams.length > 0 ? `?${eparams}` : "";
|
||||
})()}
|
||||
/>
|
||||
)}
|
||||
{providers.length == 0 && (
|
||||
|
||||
@@ -57,6 +57,16 @@ export default defineConfig({
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/robots.txt/, ""),
|
||||
},
|
||||
"/authorize": {
|
||||
target: "http://tinyauth-backend:3000/authorize",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/authorize/, ""),
|
||||
bypass: (req) => {
|
||||
if (req.method === "GET") {
|
||||
return "/index.html";
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
allowedHosts: true,
|
||||
},
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
type EnvEntry struct {
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
type MarkdownEntry struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module github.com/steveiliop56/tinyauth
|
||||
module github.com/tinyauthapp/tinyauth
|
||||
|
||||
go 1.26.0
|
||||
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/mdp/qrterminal/v3 v3.2.1
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/rs/zerolog v1.35.0
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
||||
github.com/weppos/publicsuffix-go v0.50.3
|
||||
@@ -22,7 +22,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
modernc.org/sqlite v1.48.2
|
||||
modernc.org/sqlite v1.49.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -30,7 +30,7 @@ require (
|
||||
charm.land/bubbletea/v2 v2.0.2 // indirect
|
||||
charm.land/lipgloss/v2 v2.0.1 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.1.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.1.1 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
@@ -124,7 +124,7 @@ require (
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/libc v1.72.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
|
||||
@@ -10,8 +10,8 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
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-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
|
||||
github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
|
||||
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
@@ -234,8 +234,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
|
||||
github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
@@ -329,10 +329,10 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
|
||||
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
|
||||
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
||||
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
||||
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
||||
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
@@ -341,8 +341,8 @@ modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -351,8 +351,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c=
|
||||
modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||
modernc.org/sqlite v1.49.1 h1:dYGHTKcX1sJ+EQDnUzvz4TJ5GbuvhNJa8Fg6ElGx73U=
|
||||
modernc.org/sqlite v1.49.1/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "given_name";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "family_name";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "middle_name";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "nickname";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "profile";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "picture";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "website";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "gender";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "birthdate";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "zoneinfo";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "locale";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "phone_number";
|
||||
ALTER TABLE "oidc_userinfo" DROP COLUMN "address";
|
||||
@@ -0,0 +1,13 @@
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "given_name" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "family_name" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "middle_name" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "nickname" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "profile" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "picture" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "website" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "gender" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "birthdate" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "zoneinfo" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "locale" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "phone_number" TEXT NOT NULL DEFAULT "";
|
||||
ALTER TABLE "oidc_userinfo" ADD COLUMN "address" TEXT NOT NULL DEFAULT "{}";
|
||||
@@ -12,11 +12,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
type BootstrapApp struct {
|
||||
@@ -44,9 +44,11 @@ func NewBootstrapApp(config config.Config) *BootstrapApp {
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) Setup() error {
|
||||
fmt.Println("Tinyauth is moving to an organization! All versions after v5.0.7 will be released under ghcr.io/tinyauthapp/tinyauth. Existing images will continue to work but new features and updates (including security ones) will only be released under the new image path.")
|
||||
|
||||
// get app url
|
||||
if app.config.AppURL == "" {
|
||||
return fmt.Errorf("app URL cannot be empty, perhaps config loading failed")
|
||||
}
|
||||
|
||||
appUrl, err := url.Parse(app.config.AppURL)
|
||||
|
||||
if err != nil {
|
||||
@@ -61,7 +63,7 @@ func (app *BootstrapApp) Setup() error {
|
||||
}
|
||||
|
||||
// Parse users
|
||||
users, err := utils.GetUsers(app.config.Auth.Users, app.config.Auth.UsersFile)
|
||||
users, err := utils.GetUsers(app.config.Auth.Users, app.config.Auth.UsersFile, app.config.Auth.UserAttributes)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/assets"
|
||||
"github.com/tinyauthapp/tinyauth/internal/assets"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/middleware"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -87,7 +87,7 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
|
||||
|
||||
oauthController.SetupRoutes()
|
||||
|
||||
oidcController := controller.NewOIDCController(controller.OIDCControllerConfig{}, app.services.oidcService, apiRouter)
|
||||
oidcController := controller.NewOIDCController(controller.OIDCControllerConfig{}, app.services.oidcService, apiRouter, engine)
|
||||
|
||||
oidcController.SetupRoutes()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
type Services struct {
|
||||
|
||||
@@ -113,15 +113,43 @@ type ServerConfig struct {
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
IP IPConfig `description:"IP whitelisting config options." yaml:"ip"`
|
||||
Users []string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users"`
|
||||
UsersFile string `description:"Path to the users file." yaml:"usersFile"`
|
||||
SecureCookie bool `description:"Enable secure cookies." yaml:"secureCookie"`
|
||||
SessionExpiry int `description:"Session expiry time in seconds." yaml:"sessionExpiry"`
|
||||
SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime"`
|
||||
LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout"`
|
||||
LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries"`
|
||||
TrustedProxies []string `description:"Comma-separated list of trusted proxy addresses." yaml:"trustedProxies"`
|
||||
IP IPConfig `description:"IP whitelisting config options." yaml:"ip"`
|
||||
Users []string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users"`
|
||||
UserAttributes map[string]UserAttributes `description:"Map of per-user OIDC attributes (username -> attributes)." yaml:"userAttributes"`
|
||||
UsersFile string `description:"Path to the users file." yaml:"usersFile"`
|
||||
SecureCookie bool `description:"Enable secure cookies." yaml:"secureCookie"`
|
||||
SessionExpiry int `description:"Session expiry time in seconds." yaml:"sessionExpiry"`
|
||||
SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime"`
|
||||
LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout"`
|
||||
LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries"`
|
||||
TrustedProxies []string `description:"Comma-separated list of trusted proxy addresses." yaml:"trustedProxies"`
|
||||
}
|
||||
|
||||
type UserAttributes struct {
|
||||
Name string `description:"Full name of the user." yaml:"name"`
|
||||
GivenName string `description:"Given (first) name of the user." yaml:"givenName"`
|
||||
FamilyName string `description:"Family (last) name of the user." yaml:"familyName"`
|
||||
MiddleName string `description:"Middle name of the user." yaml:"middleName"`
|
||||
Nickname string `description:"Nickname of the user." yaml:"nickname"`
|
||||
Profile string `description:"URL of the user's profile page." yaml:"profile"`
|
||||
Picture string `description:"URL of the user's profile picture." yaml:"picture"`
|
||||
Website string `description:"URL of the user's website." yaml:"website"`
|
||||
Email string `description:"Email address of the user." yaml:"email"`
|
||||
Gender string `description:"Gender of the user." yaml:"gender"`
|
||||
Birthdate string `description:"Birthdate of the user (YYYY-MM-DD)." yaml:"birthdate"`
|
||||
Zoneinfo string `description:"Time zone of the user (e.g. Europe/Athens)." yaml:"zoneinfo"`
|
||||
Locale string `description:"Locale of the user (e.g. en-US)." yaml:"locale"`
|
||||
PhoneNumber string `description:"Phone number of the user." yaml:"phoneNumber"`
|
||||
Address AddressClaim `description:"Address of the user." yaml:"address"`
|
||||
}
|
||||
|
||||
type AddressClaim struct {
|
||||
Formatted string `description:"Full mailing address, formatted for display." yaml:"formatted" json:"formatted,omitempty"`
|
||||
StreetAddress string `description:"Street address." yaml:"streetAddress" json:"street_address,omitempty"`
|
||||
Locality string `description:"City or locality." yaml:"locality" json:"locality,omitempty"`
|
||||
Region string `description:"State, province, or region." yaml:"region" json:"region,omitempty"`
|
||||
PostalCode string `description:"Zip or postal code." yaml:"postalCode" json:"postal_code,omitempty"`
|
||||
Country string `description:"Country." yaml:"country" json:"country,omitempty"`
|
||||
}
|
||||
|
||||
type IPConfig struct {
|
||||
@@ -228,6 +256,7 @@ type User struct {
|
||||
Username string
|
||||
Password string
|
||||
TotpSecret string
|
||||
Attributes UserAttributes
|
||||
}
|
||||
|
||||
type LdapUser struct {
|
||||
@@ -254,6 +283,7 @@ type UserContext struct {
|
||||
OAuthName string
|
||||
OAuthSub string
|
||||
LdapGroups string
|
||||
Attributes UserAttributes
|
||||
}
|
||||
|
||||
// API responses and queries
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-querystring/query"
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -10,9 +11,9 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-querystring/query"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
type OIDCControllerConfig struct{}
|
||||
@@ -21,6 +22,7 @@ type OIDCController struct {
|
||||
config OIDCControllerConfig
|
||||
router *gin.RouterGroup
|
||||
oidc *service.OIDCService
|
||||
engine *gin.Engine
|
||||
}
|
||||
|
||||
type AuthorizeCallback struct {
|
||||
@@ -57,11 +59,12 @@ type ClientCredentials struct {
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
func NewOIDCController(config OIDCControllerConfig, oidcService *service.OIDCService, router *gin.RouterGroup) *OIDCController {
|
||||
func NewOIDCController(config OIDCControllerConfig, oidcService *service.OIDCService, router *gin.RouterGroup, engine *gin.Engine) *OIDCController {
|
||||
return &OIDCController{
|
||||
config: config,
|
||||
oidc: oidcService,
|
||||
router: router,
|
||||
engine: engine,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +75,7 @@ func (controller *OIDCController) SetupRoutes() {
|
||||
oidcGroup.POST("/token", controller.Token)
|
||||
oidcGroup.GET("/userinfo", controller.Userinfo)
|
||||
oidcGroup.POST("/userinfo", controller.Userinfo)
|
||||
controller.engine.POST("/authorize", controller.AuthorizePseudoPost)
|
||||
}
|
||||
|
||||
func (controller *OIDCController) GetClientInfo(c *gin.Context) {
|
||||
@@ -195,6 +199,18 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// Pseudo handler that will just redirect to get in frontend then back to backend
|
||||
func (controller *OIDCController) AuthorizePseudoPost(c *gin.Context) {
|
||||
body, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
tlog.App.Error().Err(err).Msg("Failed to read request body")
|
||||
c.Redirect(http.StatusFound, fmt.Sprintf("%s/authorize", controller.oidc.GetIssuer()))
|
||||
return
|
||||
}
|
||||
redirectUrl := fmt.Sprintf("%s/authorize?%s", controller.oidc.GetIssuer(), body)
|
||||
c.Redirect(http.StatusFound, redirectUrl)
|
||||
}
|
||||
|
||||
func (controller *OIDCController) Token(c *gin.Context) {
|
||||
if !controller.oidc.IsConfigured() {
|
||||
tlog.App.Warn().Msg("OIDC not configured")
|
||||
|
||||
@@ -12,14 +12,14 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/steveiliop56/tinyauth/internal/bootstrap"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
func TestOIDCController(t *testing.T) {
|
||||
@@ -846,6 +846,34 @@ func TestOIDCController(t *testing.T) {
|
||||
assert.Equal(t, "invalid_grant", res["error"])
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Test authorize request with POST method",
|
||||
middlewares: []gin.HandlerFunc{
|
||||
simpleCtx,
|
||||
},
|
||||
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||
body := service.AuthorizeRequest{
|
||||
Scope: "openid",
|
||||
ResponseType: "code",
|
||||
ClientID: "some-client-id",
|
||||
RedirectURI: "https://test.example.com/callback",
|
||||
State: "some-state",
|
||||
Nonce: "some-nonce",
|
||||
CodeChallenge: "some-challenge",
|
||||
CodeChallengeMethod: "plain",
|
||||
}
|
||||
queries, err := query.Values(body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("POST", "/authorize", strings.NewReader(string(queries.Encode())))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
router.ServeHTTP(recorder, req)
|
||||
assert.Equal(t, 302, recorder.Code)
|
||||
location := recorder.Header().Get("Location")
|
||||
assert.NotEmpty(t, location)
|
||||
assert.Equal(t, "https://tinyauth.example.com/authorize?client_id=some-client-id&code_challenge=some-challenge&code_challenge_method=plain&nonce=some-nonce&redirect_uri=https%3A%2F%2Ftest.example.com%2Fcallback&response_type=code&scope=openid&state=some-state", location)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app := bootstrap.NewBootstrapApp(config.Config{})
|
||||
@@ -869,7 +897,7 @@ func TestOIDCController(t *testing.T) {
|
||||
group := router.Group("/api")
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
oidcController := controller.NewOIDCController(controllerCfg, oidcService, group)
|
||||
oidcController := controller.NewOIDCController(controllerCfg, oidcService, group, router)
|
||||
oidcController.SetupRoutes()
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-querystring/query"
|
||||
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/tinyauth/internal/bootstrap"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
@@ -105,16 +106,32 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
|
||||
controller.auth.RecordLoginAttempt(req.Username, true)
|
||||
|
||||
var localUser *config.User
|
||||
if userSearch.Type == "local" {
|
||||
user := controller.auth.GetLocalUser(userSearch.Username)
|
||||
localUser = &user
|
||||
}
|
||||
|
||||
if userSearch.Type == "local" && localUser != nil {
|
||||
user := *localUser
|
||||
|
||||
if user.TotpSecret != "" {
|
||||
tlog.App.Debug().Str("username", req.Username).Msg("User has TOTP enabled, requiring TOTP verification")
|
||||
|
||||
name := user.Attributes.Name
|
||||
if name == "" {
|
||||
name = utils.Capitalize(user.Username)
|
||||
}
|
||||
|
||||
email := user.Attributes.Email
|
||||
if email == "" {
|
||||
email = utils.CompileUserEmail(user.Username, controller.config.CookieDomain)
|
||||
}
|
||||
|
||||
err := controller.auth.CreateSessionCookie(c, &repository.Session{
|
||||
Username: user.Username,
|
||||
Name: utils.Capitalize(user.Username),
|
||||
Email: utils.CompileUserEmail(user.Username, controller.config.CookieDomain),
|
||||
Name: name,
|
||||
Email: email,
|
||||
Provider: "local",
|
||||
TotpPending: true,
|
||||
})
|
||||
@@ -144,6 +161,15 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
Provider: "local",
|
||||
}
|
||||
|
||||
if userSearch.Type == "local" && localUser != nil {
|
||||
if localUser.Attributes.Name != "" {
|
||||
sessionCookie.Name = localUser.Attributes.Name
|
||||
}
|
||||
if localUser.Attributes.Email != "" {
|
||||
sessionCookie.Email = localUser.Attributes.Email
|
||||
}
|
||||
}
|
||||
|
||||
if userSearch.Type == "ldap" {
|
||||
sessionCookie.Provider = "ldap"
|
||||
}
|
||||
@@ -258,6 +284,13 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
Provider: "local",
|
||||
}
|
||||
|
||||
if user.Attributes.Name != "" {
|
||||
sessionCookie.Name = user.Attributes.Name
|
||||
}
|
||||
if user.Attributes.Email != "" {
|
||||
sessionCookie.Email = user.Attributes.Email
|
||||
}
|
||||
|
||||
tlog.App.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
|
||||
err = controller.auth.CreateSessionCookie(c, &sessionCookie)
|
||||
|
||||
@@ -4,19 +4,18 @@ import (
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/steveiliop56/tinyauth/internal/bootstrap"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -36,6 +35,23 @@ func TestUserController(t *testing.T) {
|
||||
Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password
|
||||
TotpSecret: "JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK",
|
||||
},
|
||||
{
|
||||
Username: "attruser",
|
||||
Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password
|
||||
Attributes: config.UserAttributes{
|
||||
Name: "Alice Smith",
|
||||
Email: "alice@example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Username: "attrtotpuser",
|
||||
Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password
|
||||
TotpSecret: "JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK",
|
||||
Attributes: config.UserAttributes{
|
||||
Name: "Bob Jones",
|
||||
Email: "bob@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
SessionExpiry: 10, // 10 seconds, useful for testing
|
||||
CookieDomain: "example.com",
|
||||
@@ -273,6 +289,64 @@ func TestUserController(t *testing.T) {
|
||||
assert.Contains(t, recorder.Body.String(), "Too many failed TOTP attempts.")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Login uses name and email from user attributes",
|
||||
middlewares: []gin.HandlerFunc{},
|
||||
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||
loginReq := controller.LoginRequest{Username: "attruser", Password: "password"}
|
||||
body, err := json.Marshal(loginReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/user/login", strings.NewReader(string(body)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
require.Equal(t, 200, recorder.Code)
|
||||
cookies := recorder.Result().Cookies()
|
||||
require.Len(t, cookies, 1)
|
||||
assert.Equal(t, "tinyauth-session", cookies[0].Name)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Login with TOTP uses name and email from user attributes in pending session",
|
||||
middlewares: []gin.HandlerFunc{},
|
||||
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||
loginReq := controller.LoginRequest{Username: "attrtotpuser", Password: "password"}
|
||||
body, err := json.Marshal(loginReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/user/login", strings.NewReader(string(body)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
require.Equal(t, 200, recorder.Code)
|
||||
var res map[string]any
|
||||
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
|
||||
assert.Equal(t, true, res["totpPending"])
|
||||
require.Len(t, recorder.Result().Cookies(), 1)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "TOTP completion uses name and email from user attributes",
|
||||
middlewares: []gin.HandlerFunc{},
|
||||
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
|
||||
code, err := totp.GenerateCode("JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
totpReq := controller.TotpRequest{Code: code}
|
||||
body, err := json.Marshal(totpReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/user/totp", strings.NewReader(string(body)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
require.Equal(t, 200, recorder.Code)
|
||||
cookies := recorder.Result().Cookies()
|
||||
require.Len(t, cookies, 1)
|
||||
assert.Equal(t, "tinyauth-session", cookies[0].Name)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
oauthBrokerCfgs := make(map[string]config.OAuthServiceConfig)
|
||||
@@ -305,9 +379,31 @@ func TestUserController(t *testing.T) {
|
||||
authService.ClearRateLimitsTestingOnly()
|
||||
}
|
||||
|
||||
setTotpMiddlewareOverrides := []string{
|
||||
"Should be able to login with totp",
|
||||
"Totp should rate limit on multiple invalid attempts",
|
||||
setTotpMiddlewareOverrides := map[string]config.UserContext{
|
||||
"Should be able to login with totp": {
|
||||
Username: "totpuser",
|
||||
Name: "Totpuser",
|
||||
Email: "totpuser@example.com",
|
||||
Provider: "local",
|
||||
TotpPending: true,
|
||||
TotpEnabled: true,
|
||||
},
|
||||
"Totp should rate limit on multiple invalid attempts": {
|
||||
Username: "totpuser",
|
||||
Name: "Totpuser",
|
||||
Email: "totpuser@example.com",
|
||||
Provider: "local",
|
||||
TotpPending: true,
|
||||
TotpEnabled: true,
|
||||
},
|
||||
"TOTP completion uses name and email from user attributes": {
|
||||
Username: "attrtotpuser",
|
||||
Name: "Bob Jones",
|
||||
Email: "bob@example.com",
|
||||
Provider: "local",
|
||||
TotpPending: true,
|
||||
TotpEnabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -321,18 +417,10 @@ func TestUserController(t *testing.T) {
|
||||
|
||||
// Gin is stupid and doesn't allow setting a middleware after the groups
|
||||
// so we need to do some stupid overrides here
|
||||
if slices.Contains(setTotpMiddlewareOverrides, test.description) {
|
||||
// Assuming the cookie is set, it should be picked up by the
|
||||
// context middleware
|
||||
if ctx, ok := setTotpMiddlewareOverrides[test.description]; ok {
|
||||
ctx := ctx
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: "totpuser",
|
||||
Name: "Totpuser",
|
||||
Email: "totpuser@example.com",
|
||||
Provider: "local",
|
||||
TotpPending: true,
|
||||
TotpEnabled: true,
|
||||
})
|
||||
c.Set("context", &ctx)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
)
|
||||
|
||||
type OpenIDConnectConfiguration struct {
|
||||
@@ -61,7 +61,7 @@ func (controller *WellKnownController) OpenIDConnectConfiguration(c *gin.Context
|
||||
SubjectTypesSupported: []string{"pairwise"},
|
||||
IDTokenSigningAlgValuesSupported: []string{"RS256"},
|
||||
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "client_secret_post"},
|
||||
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "email_verified", "groups"},
|
||||
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "email_verified", "groups", "phone_number", "phone_number_verified", "address", "given_name", "family_name", "middle_name", "nickname", "profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale"},
|
||||
ServiceDocumentation: "https://tinyauth.app/docs/guides/oidc",
|
||||
RequestParameterSupported: true,
|
||||
RequestObjectSigningAlgValuesSupported: []string{"none"},
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/tinyauth/internal/bootstrap"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/controller"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -67,7 +67,7 @@ func TestWellKnownController(t *testing.T) {
|
||||
SubjectTypesSupported: []string{"pairwise"},
|
||||
IDTokenSigningAlgValuesSupported: []string{"RS256"},
|
||||
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "client_secret_post"},
|
||||
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "email_verified", "groups"},
|
||||
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "email_verified", "groups", "phone_number", "phone_number_verified", "address", "given_name", "family_name", "middle_name", "nickname", "profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale"},
|
||||
ServiceDocumentation: "https://tinyauth.app/docs/guides/oidc",
|
||||
RequestParameterSupported: true,
|
||||
RequestObjectSigningAlgValuesSupported: []string{"none"},
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -99,6 +99,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
var ldapGroups []string
|
||||
var localAttributes config.UserAttributes
|
||||
|
||||
if cookie.Provider == "ldap" {
|
||||
ldapUser, err := m.auth.GetLdapUser(userSearch.Username)
|
||||
@@ -112,6 +113,11 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
ldapGroups = ldapUser.Groups
|
||||
}
|
||||
|
||||
if cookie.Provider == "local" {
|
||||
localUser := m.auth.GetLocalUser(cookie.Username)
|
||||
localAttributes = localUser.Attributes
|
||||
}
|
||||
|
||||
m.auth.RefreshSessionCookie(c)
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: cookie.Username,
|
||||
@@ -120,6 +126,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
Provider: cookie.Provider,
|
||||
IsLoggedIn: true,
|
||||
LdapGroups: strings.Join(ldapGroups, ","),
|
||||
Attributes: localAttributes,
|
||||
})
|
||||
c.Next()
|
||||
return
|
||||
@@ -202,13 +209,23 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
name := utils.Capitalize(user.Username)
|
||||
if user.Attributes.Name != "" {
|
||||
name = user.Attributes.Name
|
||||
}
|
||||
email := utils.CompileUserEmail(user.Username, m.config.CookieDomain)
|
||||
if user.Attributes.Email != "" {
|
||||
email = user.Attributes.Email
|
||||
}
|
||||
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: user.Username,
|
||||
Name: utils.Capitalize(user.Username),
|
||||
Email: utils.CompileUserEmail(user.Username, m.config.CookieDomain),
|
||||
Name: name,
|
||||
Email: email,
|
||||
Provider: "local",
|
||||
IsLoggedIn: true,
|
||||
IsBasicAuth: true,
|
||||
Attributes: user.Attributes,
|
||||
})
|
||||
c.Next()
|
||||
return
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/assets"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/assets"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -39,6 +39,7 @@ func (m *UIMiddleware) Init() error {
|
||||
func (m *UIMiddleware) Middleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
path := strings.TrimPrefix(c.Request.URL.Path, "/")
|
||||
method := c.Request.Method
|
||||
|
||||
tlog.App.Debug().Str("path", path).Msg("path")
|
||||
|
||||
@@ -52,6 +53,12 @@ func (m *UIMiddleware) Middleware() gin.HandlerFunc {
|
||||
c.Writer.Write([]byte("User-agent: *\nDisallow: /\n"))
|
||||
return
|
||||
default:
|
||||
// For OIDC post authentication, we need to redirect the POST to /authorize to the backend
|
||||
if method == http.MethodPost && strings.HasPrefix(path, "authorize") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
_, err := fs.Stat(m.uiFs, path)
|
||||
|
||||
// Enough for one authentication flow
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
// See context middleware for explanation of why we have to do this
|
||||
|
||||
@@ -34,6 +34,19 @@ type OidcUserinfo struct {
|
||||
Email string
|
||||
Groups string
|
||||
UpdatedAt int64
|
||||
GivenName string
|
||||
FamilyName string
|
||||
MiddleName string
|
||||
Nickname string
|
||||
Profile string
|
||||
Picture string
|
||||
Website string
|
||||
Gender string
|
||||
Birthdate string
|
||||
Zoneinfo string
|
||||
Locale string
|
||||
PhoneNumber string
|
||||
Address string
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
|
||||
@@ -124,11 +124,24 @@ INSERT INTO "oidc_userinfo" (
|
||||
"preferred_username",
|
||||
"email",
|
||||
"groups",
|
||||
"updated_at"
|
||||
"updated_at",
|
||||
"given_name",
|
||||
"family_name",
|
||||
"middle_name",
|
||||
"nickname",
|
||||
"profile",
|
||||
"picture",
|
||||
"website",
|
||||
"gender",
|
||||
"birthdate",
|
||||
"zoneinfo",
|
||||
"locale",
|
||||
"phone_number",
|
||||
"address"
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING sub, name, preferred_username, email, "groups", updated_at
|
||||
RETURNING sub, name, preferred_username, email, "groups", updated_at, given_name, family_name, middle_name, nickname, profile, picture, website, gender, birthdate, zoneinfo, locale, phone_number, address
|
||||
`
|
||||
|
||||
type CreateOidcUserInfoParams struct {
|
||||
@@ -138,6 +151,19 @@ type CreateOidcUserInfoParams struct {
|
||||
Email string
|
||||
Groups string
|
||||
UpdatedAt int64
|
||||
GivenName string
|
||||
FamilyName string
|
||||
MiddleName string
|
||||
Nickname string
|
||||
Profile string
|
||||
Picture string
|
||||
Website string
|
||||
Gender string
|
||||
Birthdate string
|
||||
Zoneinfo string
|
||||
Locale string
|
||||
PhoneNumber string
|
||||
Address string
|
||||
}
|
||||
|
||||
func (q *Queries) CreateOidcUserInfo(ctx context.Context, arg CreateOidcUserInfoParams) (OidcUserinfo, error) {
|
||||
@@ -148,6 +174,19 @@ func (q *Queries) CreateOidcUserInfo(ctx context.Context, arg CreateOidcUserInfo
|
||||
arg.Email,
|
||||
arg.Groups,
|
||||
arg.UpdatedAt,
|
||||
arg.GivenName,
|
||||
arg.FamilyName,
|
||||
arg.MiddleName,
|
||||
arg.Nickname,
|
||||
arg.Profile,
|
||||
arg.Picture,
|
||||
arg.Website,
|
||||
arg.Gender,
|
||||
arg.Birthdate,
|
||||
arg.Zoneinfo,
|
||||
arg.Locale,
|
||||
arg.PhoneNumber,
|
||||
arg.Address,
|
||||
)
|
||||
var i OidcUserinfo
|
||||
err := row.Scan(
|
||||
@@ -157,6 +196,19 @@ func (q *Queries) CreateOidcUserInfo(ctx context.Context, arg CreateOidcUserInfo
|
||||
&i.Email,
|
||||
&i.Groups,
|
||||
&i.UpdatedAt,
|
||||
&i.GivenName,
|
||||
&i.FamilyName,
|
||||
&i.MiddleName,
|
||||
&i.Nickname,
|
||||
&i.Profile,
|
||||
&i.Picture,
|
||||
&i.Website,
|
||||
&i.Gender,
|
||||
&i.Birthdate,
|
||||
&i.Zoneinfo,
|
||||
&i.Locale,
|
||||
&i.PhoneNumber,
|
||||
&i.Address,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -456,7 +508,7 @@ func (q *Queries) GetOidcTokenBySub(ctx context.Context, sub string) (OidcToken,
|
||||
}
|
||||
|
||||
const getOidcUserInfo = `-- name: GetOidcUserInfo :one
|
||||
SELECT sub, name, preferred_username, email, "groups", updated_at FROM "oidc_userinfo"
|
||||
SELECT sub, name, preferred_username, email, "groups", updated_at, given_name, family_name, middle_name, nickname, profile, picture, website, gender, birthdate, zoneinfo, locale, phone_number, address FROM "oidc_userinfo"
|
||||
WHERE "sub" = ?
|
||||
`
|
||||
|
||||
@@ -470,6 +522,19 @@ func (q *Queries) GetOidcUserInfo(ctx context.Context, sub string) (OidcUserinfo
|
||||
&i.Email,
|
||||
&i.Groups,
|
||||
&i.UpdatedAt,
|
||||
&i.GivenName,
|
||||
&i.FamilyName,
|
||||
&i.MiddleName,
|
||||
&i.Nickname,
|
||||
&i.Profile,
|
||||
&i.Picture,
|
||||
&i.Website,
|
||||
&i.Gender,
|
||||
&i.Birthdate,
|
||||
&i.Zoneinfo,
|
||||
&i.Locale,
|
||||
&i.PhoneNumber,
|
||||
&i.Address,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
type AccessControlsService struct {
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/decoders"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
container "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/cenkalti/backoff/v5"
|
||||
ldapgo "github.com/go-ldap/ldap/v3"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
type LdapServiceConfig struct {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
type GithubEmailResponse []struct {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"golang.org/x/oauth2/endpoints"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
||||
@@ -20,15 +20,15 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var (
|
||||
SupportedScopes = []string{"openid", "profile", "email", "groups"}
|
||||
SupportedScopes = []string{"openid", "profile", "email", "phone", "address", "groups"}
|
||||
SupportedResponseTypes = []string{"code"}
|
||||
SupportedGrantTypes = []string{"authorization_code", "refresh_token"}
|
||||
)
|
||||
@@ -48,6 +48,17 @@ type ClaimSet struct {
|
||||
Iat int64 `json:"iat"`
|
||||
Exp int64 `json:"exp"`
|
||||
Name string `json:"name,omitempty"`
|
||||
GivenName string `json:"given_name,omitempty"`
|
||||
FamilyName string `json:"family_name,omitempty"`
|
||||
MiddleName string `json:"middle_name,omitempty"`
|
||||
Nickname string `json:"nickname,omitempty"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
Picture string `json:"picture,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
Gender string `json:"gender,omitempty"`
|
||||
Birthdate string `json:"birthdate,omitempty"`
|
||||
Zoneinfo string `json:"zoneinfo,omitempty"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||
@@ -56,13 +67,27 @@ type ClaimSet struct {
|
||||
}
|
||||
|
||||
type UserinfoResponse struct {
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name,omitempty"`
|
||||
GivenName string `json:"given_name,omitempty"`
|
||||
FamilyName string `json:"family_name,omitempty"`
|
||||
MiddleName string `json:"middle_name,omitempty"`
|
||||
Nickname string `json:"nickname,omitempty"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
Picture string `json:"picture,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
Gender string `json:"gender,omitempty"`
|
||||
Birthdate string `json:"birthdate,omitempty"`
|
||||
Zoneinfo string `json:"zoneinfo,omitempty"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
PhoneNumber string `json:"phone_number,omitempty"`
|
||||
PhoneNumberVerified *bool `json:"phone_number_verified,omitempty"`
|
||||
Address *config.AddressClaim `json:"address,omitempty"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
type TokenResponse struct {
|
||||
@@ -75,14 +100,15 @@ type TokenResponse struct {
|
||||
}
|
||||
|
||||
type AuthorizeRequest struct {
|
||||
Scope string `json:"scope" binding:"required"`
|
||||
ResponseType string `json:"response_type" binding:"required"`
|
||||
ClientID string `json:"client_id" binding:"required"`
|
||||
RedirectURI string `json:"redirect_uri" binding:"required"`
|
||||
State string `json:"state"`
|
||||
Nonce string `json:"nonce"`
|
||||
CodeChallenge string `json:"code_challenge"`
|
||||
CodeChallengeMethod string `json:"code_challenge_method"`
|
||||
Scope string `json:"scope" binding:"required" url:"scope"`
|
||||
ResponseType string `json:"response_type" binding:"required" url:"response_type"`
|
||||
ClientID string `json:"client_id" binding:"required" url:"client_id"`
|
||||
RedirectURI string `json:"redirect_uri" binding:"required" url:"redirect_uri"`
|
||||
State string `json:"state" url:"state"`
|
||||
Nonce string `json:"nonce" url:"nonce"`
|
||||
CodeChallenge string `json:"code_challenge" url:"code_challenge"`
|
||||
CodeChallengeMethod string `json:"code_challenge_method" url:"code_challenge_method"`
|
||||
Request string `json:"request" url:"request"`
|
||||
}
|
||||
|
||||
type OIDCServiceConfig struct {
|
||||
@@ -342,12 +368,30 @@ func (service *OIDCService) StoreCode(c *gin.Context, sub string, code string, r
|
||||
}
|
||||
|
||||
func (service *OIDCService) StoreUserinfo(c *gin.Context, sub string, userContext config.UserContext, req AuthorizeRequest) error {
|
||||
addressJSON, err := json.Marshal(userContext.Attributes.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userInfoParams := repository.CreateOidcUserInfoParams{
|
||||
Sub: sub,
|
||||
Name: userContext.Name,
|
||||
Email: userContext.Email,
|
||||
PreferredUsername: userContext.Username,
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
GivenName: userContext.Attributes.GivenName,
|
||||
FamilyName: userContext.Attributes.FamilyName,
|
||||
MiddleName: userContext.Attributes.MiddleName,
|
||||
Nickname: userContext.Attributes.Nickname,
|
||||
Profile: userContext.Attributes.Profile,
|
||||
Picture: userContext.Attributes.Picture,
|
||||
Website: userContext.Attributes.Website,
|
||||
Gender: userContext.Attributes.Gender,
|
||||
Birthdate: userContext.Attributes.Birthdate,
|
||||
Zoneinfo: userContext.Attributes.Zoneinfo,
|
||||
Locale: userContext.Attributes.Locale,
|
||||
PhoneNumber: userContext.Attributes.PhoneNumber,
|
||||
Address: string(addressJSON),
|
||||
}
|
||||
|
||||
// Tinyauth will pass through the groups it got from an LDAP or an OIDC server
|
||||
@@ -359,7 +403,7 @@ func (service *OIDCService) StoreUserinfo(c *gin.Context, sub string, userContex
|
||||
userInfoParams.Groups = userContext.OAuthGroups
|
||||
}
|
||||
|
||||
_, err := service.queries.CreateOidcUserInfo(c, userInfoParams)
|
||||
_, err = service.queries.CreateOidcUserInfo(c, userInfoParams)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -637,12 +681,22 @@ func (service *OIDCService) CompileUserinfo(user repository.OidcUserinfo, scope
|
||||
if slices.Contains(scopes, "profile") {
|
||||
userInfo.Name = user.Name
|
||||
userInfo.PreferredUsername = user.PreferredUsername
|
||||
userInfo.GivenName = user.GivenName
|
||||
userInfo.FamilyName = user.FamilyName
|
||||
userInfo.MiddleName = user.MiddleName
|
||||
userInfo.Nickname = user.Nickname
|
||||
userInfo.Profile = user.Profile
|
||||
userInfo.Picture = user.Picture
|
||||
userInfo.Website = user.Website
|
||||
userInfo.Gender = user.Gender
|
||||
userInfo.Birthdate = user.Birthdate
|
||||
userInfo.Zoneinfo = user.Zoneinfo
|
||||
userInfo.Locale = user.Locale
|
||||
}
|
||||
|
||||
if slices.Contains(scopes, "email") {
|
||||
userInfo.Email = user.Email
|
||||
// We can set this as a configuration option in the future but for now it's a good idea to assume it's true
|
||||
userInfo.EmailVerified = true
|
||||
userInfo.EmailVerified = user.Email != ""
|
||||
}
|
||||
|
||||
if slices.Contains(scopes, "groups") {
|
||||
@@ -653,6 +707,19 @@ func (service *OIDCService) CompileUserinfo(user repository.OidcUserinfo, scope
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(scopes, "phone") {
|
||||
userInfo.PhoneNumber = user.PhoneNumber
|
||||
verified := user.PhoneNumber != ""
|
||||
userInfo.PhoneNumberVerified = &verified
|
||||
}
|
||||
|
||||
if slices.Contains(scopes, "address") {
|
||||
var addr config.AddressClaim
|
||||
if err := json.Unmarshal([]byte(user.Address), &addr); err == nil {
|
||||
userInfo.Address = &addr
|
||||
}
|
||||
}
|
||||
|
||||
return userInfo
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
"github.com/tinyauthapp/tinyauth/internal/service"
|
||||
)
|
||||
|
||||
func newTestUser() repository.OidcUserinfo {
|
||||
addr := config.AddressClaim{
|
||||
Formatted: "123 Main St",
|
||||
StreetAddress: "123 Main St",
|
||||
Locality: "Springfield",
|
||||
Region: "IL",
|
||||
PostalCode: "62701",
|
||||
Country: "US",
|
||||
}
|
||||
addrJSON, _ := json.Marshal(addr)
|
||||
|
||||
return repository.OidcUserinfo{
|
||||
Sub: "test-sub",
|
||||
Name: "Test User",
|
||||
PreferredUsername: "testuser",
|
||||
Email: "test@example.com",
|
||||
Groups: "admins,users",
|
||||
UpdatedAt: 1234567890,
|
||||
GivenName: "Test",
|
||||
FamilyName: "User",
|
||||
MiddleName: "M",
|
||||
Nickname: "testy",
|
||||
Profile: "https://example.com/testuser",
|
||||
Picture: "https://example.com/testuser.jpg",
|
||||
Website: "https://testuser.example.com",
|
||||
Gender: "male",
|
||||
Birthdate: "1990-01-01",
|
||||
Zoneinfo: "America/Chicago",
|
||||
Locale: "en-US",
|
||||
PhoneNumber: "+15555550100",
|
||||
Address: string(addrJSON),
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileUserinfo(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
svc := service.NewOIDCService(service.OIDCServiceConfig{
|
||||
PrivateKeyPath: dir + "/key.pem",
|
||||
PublicKeyPath: dir + "/key.pub",
|
||||
Issuer: "https://tinyauth.example.com",
|
||||
SessionExpiry: 3600,
|
||||
}, nil)
|
||||
require.NoError(t, svc.Init())
|
||||
|
||||
type testCase struct {
|
||||
description string
|
||||
mutate func(u *repository.OidcUserinfo)
|
||||
scope string
|
||||
run func(t *testing.T, info service.UserinfoResponse)
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
description: "openid scope only returns sub and updated_at",
|
||||
scope: "openid",
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
assert.Equal(t, "test-sub", info.Sub)
|
||||
assert.Equal(t, int64(1234567890), info.UpdatedAt)
|
||||
assert.Empty(t, info.Name)
|
||||
assert.Empty(t, info.Email)
|
||||
assert.Nil(t, info.Groups)
|
||||
assert.Nil(t, info.PhoneNumberVerified)
|
||||
assert.Nil(t, info.Address)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "profile scope returns all profile fields",
|
||||
scope: "openid,profile",
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
assert.Equal(t, "Test User", info.Name)
|
||||
assert.Equal(t, "testuser", info.PreferredUsername)
|
||||
assert.Equal(t, "Test", info.GivenName)
|
||||
assert.Equal(t, "User", info.FamilyName)
|
||||
assert.Equal(t, "M", info.MiddleName)
|
||||
assert.Equal(t, "testy", info.Nickname)
|
||||
assert.Equal(t, "https://example.com/testuser", info.Profile)
|
||||
assert.Equal(t, "https://example.com/testuser.jpg", info.Picture)
|
||||
assert.Equal(t, "https://testuser.example.com", info.Website)
|
||||
assert.Equal(t, "male", info.Gender)
|
||||
assert.Equal(t, "1990-01-01", info.Birthdate)
|
||||
assert.Equal(t, "America/Chicago", info.Zoneinfo)
|
||||
assert.Equal(t, "en-US", info.Locale)
|
||||
assert.Empty(t, info.Email)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "email scope sets email and email_verified true when email present",
|
||||
scope: "openid,email",
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
assert.Equal(t, "test@example.com", info.Email)
|
||||
assert.True(t, info.EmailVerified)
|
||||
assert.Empty(t, info.Name)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "email scope sets email_verified false when email absent",
|
||||
scope: "openid,email",
|
||||
mutate: func(u *repository.OidcUserinfo) { u.Email = "" },
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
assert.Empty(t, info.Email)
|
||||
assert.False(t, info.EmailVerified)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "phone scope sets phone_number_verified true when phone present",
|
||||
scope: "openid,phone",
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
assert.Equal(t, "+15555550100", info.PhoneNumber)
|
||||
require.NotNil(t, info.PhoneNumberVerified)
|
||||
assert.True(t, *info.PhoneNumberVerified)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "phone scope sets phone_number_verified false when phone absent",
|
||||
scope: "openid,phone",
|
||||
mutate: func(u *repository.OidcUserinfo) { u.PhoneNumber = "" },
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
require.NotNil(t, info.PhoneNumberVerified)
|
||||
assert.False(t, *info.PhoneNumberVerified)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "address scope returns parsed address",
|
||||
scope: "openid,address",
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
require.NotNil(t, info.Address)
|
||||
assert.Equal(t, "123 Main St", info.Address.Formatted)
|
||||
assert.Equal(t, "123 Main St", info.Address.StreetAddress)
|
||||
assert.Equal(t, "Springfield", info.Address.Locality)
|
||||
assert.Equal(t, "IL", info.Address.Region)
|
||||
assert.Equal(t, "62701", info.Address.PostalCode)
|
||||
assert.Equal(t, "US", info.Address.Country)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "address scope with invalid JSON omits address",
|
||||
scope: "openid,address",
|
||||
mutate: func(u *repository.OidcUserinfo) { u.Address = "not-valid-json" },
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
assert.Nil(t, info.Address)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "groups scope returns split groups",
|
||||
scope: "openid,groups",
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
assert.Equal(t, []string{"admins", "users"}, info.Groups)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "groups scope returns empty slice when no groups",
|
||||
scope: "openid,groups",
|
||||
mutate: func(u *repository.OidcUserinfo) { u.Groups = "" },
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
assert.Equal(t, []string{}, info.Groups)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "all scopes return all fields",
|
||||
scope: "openid,profile,email,phone,address,groups",
|
||||
run: func(t *testing.T, info service.UserinfoResponse) {
|
||||
assert.Equal(t, "Test User", info.Name)
|
||||
assert.Equal(t, "test@example.com", info.Email)
|
||||
assert.Equal(t, "+15555550100", info.PhoneNumber)
|
||||
require.NotNil(t, info.PhoneNumberVerified)
|
||||
assert.True(t, *info.PhoneNumberVerified)
|
||||
require.NotNil(t, info.Address)
|
||||
assert.Equal(t, "Springfield", info.Address.Locality)
|
||||
assert.Equal(t, []string{"admins", "users"}, info.Groups)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
user := newTestUser()
|
||||
if test.mutate != nil {
|
||||
test.mutate(&user)
|
||||
}
|
||||
info := svc.CompileUserinfo(user, test.scope)
|
||||
test.run(t, info)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/weppos/publicsuffix-go/publicsuffix"
|
||||
|
||||
@@ -3,8 +3,8 @@ package utils_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
@@ -3,8 +3,8 @@ package decoders_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/decoders"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package utils_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
|
||||
"github.com/tinyauthapp/paerser/cli"
|
||||
"github.com/tinyauthapp/paerser/env"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package utils_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
func ParseUsers(usersStr []string) ([]config.User, error) {
|
||||
func ParseUsers(usersStr []string, userAttributes map[string]config.UserAttributes) ([]config.User, error) {
|
||||
var users []config.User
|
||||
|
||||
if len(usersStr) == 0 {
|
||||
@@ -24,13 +24,16 @@ func ParseUsers(usersStr []string) ([]config.User, error) {
|
||||
if err != nil {
|
||||
return []config.User{}, err
|
||||
}
|
||||
if attrs, ok := userAttributes[parsed.Username]; ok {
|
||||
parsed.Attributes = attrs
|
||||
}
|
||||
users = append(users, parsed)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetUsers(usersCfg []string, usersPath string) ([]config.User, error) {
|
||||
func GetUsers(usersCfg []string, usersPath string, userAttributes map[string]config.UserAttributes) ([]config.User, error) {
|
||||
var usersStr []string
|
||||
|
||||
if len(usersCfg) == 0 && usersPath == "" {
|
||||
@@ -59,7 +62,7 @@ func GetUsers(usersCfg []string, usersPath string) ([]config.User, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return ParseUsers(usersStr)
|
||||
return ParseUsers(usersStr, userAttributes)
|
||||
}
|
||||
|
||||
func ParseUser(userStr string) (config.User, error) {
|
||||
|
||||
@@ -4,122 +4,117 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/config"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestGetUsers(t *testing.T) {
|
||||
hash := "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G"
|
||||
|
||||
// Setup
|
||||
file, err := os.Create("/tmp/tinyauth_users_test.txt")
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = file.WriteString(" user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G \n user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G ") // Spacing is on purpose
|
||||
_, err = file.WriteString(" user1:" + hash + " \n user2:" + hash + " ") // Spacing is on purpose
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = file.Close()
|
||||
assert.NilError(t, err)
|
||||
defer os.Remove("/tmp/tinyauth_users_test.txt")
|
||||
|
||||
// Test file
|
||||
users, err := utils.GetUsers([]string{}, "/tmp/tinyauth_users_test.txt")
|
||||
noAttrs := map[string]config.UserAttributes{}
|
||||
|
||||
// Test file only
|
||||
users, err := utils.GetUsers([]string{}, "/tmp/tinyauth_users_test.txt", noAttrs)
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(users))
|
||||
|
||||
assert.Equal(t, "user1", users[0].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[0].Password)
|
||||
assert.Equal(t, hash, users[0].Password)
|
||||
assert.Equal(t, "user2", users[1].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[1].Password)
|
||||
assert.Equal(t, hash, users[1].Password)
|
||||
|
||||
// Test config
|
||||
users, err = utils.GetUsers([]string{"user3:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", "user4:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G"}, "")
|
||||
// Test inline config only
|
||||
users, err = utils.GetUsers([]string{"user3:" + hash, "user4:" + hash}, "", noAttrs)
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(users))
|
||||
|
||||
assert.Equal(t, "user3", users[0].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[0].Password)
|
||||
assert.Equal(t, "user4", users[1].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[1].Password)
|
||||
|
||||
// Test both
|
||||
users, err = utils.GetUsers([]string{"user5:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G"}, "/tmp/tinyauth_users_test.txt")
|
||||
users, err = utils.GetUsers([]string{"user5:" + hash}, "/tmp/tinyauth_users_test.txt", noAttrs)
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 3, len(users))
|
||||
|
||||
assert.Equal(t, "user5", users[0].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[0].Password)
|
||||
assert.Equal(t, "user1", users[1].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[1].Password)
|
||||
assert.Equal(t, "user2", users[2].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[2].Password)
|
||||
usernames := map[string]bool{}
|
||||
for _, u := range users {
|
||||
usernames[u.Username] = true
|
||||
}
|
||||
assert.Assert(t, usernames["user1"])
|
||||
assert.Assert(t, usernames["user2"])
|
||||
assert.Assert(t, usernames["user5"])
|
||||
|
||||
// Test attributes applied from userAttributes map
|
||||
attrs := map[string]config.UserAttributes{
|
||||
"user1": {Name: "User One", Email: "user1@example.com"},
|
||||
}
|
||||
users, err = utils.GetUsers([]string{}, "/tmp/tinyauth_users_test.txt", attrs)
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 2, len(users))
|
||||
|
||||
for _, u := range users {
|
||||
if u.Username == "user1" {
|
||||
assert.Equal(t, "User One", u.Attributes.Name)
|
||||
assert.Equal(t, "user1@example.com", u.Attributes.Email)
|
||||
}
|
||||
if u.Username == "user2" {
|
||||
assert.Equal(t, "", u.Attributes.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Test empty
|
||||
users, err = utils.GetUsers([]string{}, "")
|
||||
users, err = utils.GetUsers([]string{}, "", noAttrs)
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(users))
|
||||
|
||||
// Test non-existent file
|
||||
users, err = utils.GetUsers([]string{}, "/tmp/non_existent_file.txt")
|
||||
users, err = utils.GetUsers([]string{}, "/tmp/non_existent_file.txt", noAttrs)
|
||||
|
||||
assert.ErrorContains(t, err, "no such file or directory")
|
||||
|
||||
assert.Equal(t, 0, len(users))
|
||||
}
|
||||
|
||||
func TestParseUsers(t *testing.T) {
|
||||
// Valid users
|
||||
users, err := utils.ParseUsers([]string{"user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", "user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G:ABCDEF"}) // user2 has TOTP
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(users))
|
||||
|
||||
assert.Equal(t, "user1", users[0].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[0].Password)
|
||||
assert.Equal(t, "", users[0].TotpSecret)
|
||||
assert.Equal(t, "user2", users[1].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[1].Password)
|
||||
assert.Equal(t, "ABCDEF", users[1].TotpSecret)
|
||||
|
||||
// Valid weirdly spaced users
|
||||
users, err = utils.ParseUsers([]string{" user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G ", " user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G:ABCDEF "}) // Spacing is on purpose
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(users))
|
||||
|
||||
assert.Equal(t, "user1", users[0].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[0].Password)
|
||||
assert.Equal(t, "", users[0].TotpSecret)
|
||||
assert.Equal(t, "user2", users[1].Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", users[1].Password)
|
||||
assert.Equal(t, "ABCDEF", users[1].TotpSecret)
|
||||
}
|
||||
|
||||
func TestParseUser(t *testing.T) {
|
||||
hash := "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G"
|
||||
|
||||
// Valid user without TOTP
|
||||
user, err := utils.ParseUser("user1:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G")
|
||||
user, err := utils.ParseUser("user1:" + hash)
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, "user1", user.Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", user.Password)
|
||||
assert.Equal(t, hash, user.Password)
|
||||
assert.Equal(t, "", user.TotpSecret)
|
||||
|
||||
// Valid user with TOTP
|
||||
user, err = utils.ParseUser("user2:$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G:ABCDEF")
|
||||
user, err = utils.ParseUser("user2:" + hash + ":ABCDEF")
|
||||
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, "user2", user.Username)
|
||||
assert.Equal(t, "$2a$10$Mz5xhkfSJUtPWkzCd/TdaePh9CaXc5QcGII5wIMPLSR46eTwma30G", user.Password)
|
||||
assert.Equal(t, hash, user.Password)
|
||||
assert.Equal(t, "ABCDEF", user.TotpSecret)
|
||||
|
||||
// Valid user with $$ in password
|
||||
|
||||
+15
-2
@@ -95,9 +95,22 @@ INSERT INTO "oidc_userinfo" (
|
||||
"preferred_username",
|
||||
"email",
|
||||
"groups",
|
||||
"updated_at"
|
||||
"updated_at",
|
||||
"given_name",
|
||||
"family_name",
|
||||
"middle_name",
|
||||
"nickname",
|
||||
"profile",
|
||||
"picture",
|
||||
"website",
|
||||
"gender",
|
||||
"birthdate",
|
||||
"zoneinfo",
|
||||
"locale",
|
||||
"phone_number",
|
||||
"address"
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
|
||||
+19
-6
@@ -22,10 +22,23 @@ CREATE TABLE IF NOT EXISTS "oidc_tokens" (
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "oidc_userinfo" (
|
||||
"sub" TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"preferred_username" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"groups" TEXT NOT NULL,
|
||||
"updated_at" INTEGER NOT NULL
|
||||
"sub" TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"preferred_username" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"groups" TEXT NOT NULL,
|
||||
"updated_at" INTEGER NOT NULL,
|
||||
"given_name" TEXT NOT NULL,
|
||||
"family_name" TEXT NOT NULL,
|
||||
"middle_name" TEXT NOT NULL,
|
||||
"nickname" TEXT NOT NULL,
|
||||
"profile" TEXT NOT NULL,
|
||||
"picture" TEXT NOT NULL,
|
||||
"website" TEXT NOT NULL,
|
||||
"gender" TEXT NOT NULL,
|
||||
"birthdate" TEXT NOT NULL,
|
||||
"zoneinfo" TEXT NOT NULL,
|
||||
"locale" TEXT NOT NULL,
|
||||
"phone_number" TEXT NOT NULL,
|
||||
"address" TEXT NOT NULL
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user