Compare commits

...

11 Commits

Author SHA1 Message Date
Dreddy e8071a9d80 fix: bug fixes for issues #859, 860, 861, 862, 863, 864, 865, 866 (#867)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-05-16 17:04:01 +03:00
Ryc O'Chet 1f67797605 Update templates to use forms (#872) 2026-05-16 17:01:18 +03:00
Stavros ca06099466 tests: fix tests for proxy controller 2026-05-15 18:43:18 +03:00
Stavros d4b4245017 chore: revert 4c741a5 and use 403 for acl errors 2026-05-15 18:39:12 +03:00
Stavros 4c741a5990 fix: use 401 errors instead of 403 for nginx responses 2026-05-15 18:12:15 +03:00
Stavros def539a40f refactor: replace bun with pnpm (#870) 2026-05-15 14:43:51 +03:00
Dreddy e6b291d21c docs: enhance security policy with reporting guidelines (#868) 2026-05-14 00:08:48 +03:00
Stavros 086e3af4e2 chore: add deepsec to gitignore 2026-05-13 19:11:39 +03:00
Dreddy f9fff24ca5 fix: oidc open redirect (#854) 2026-05-13 17:34:39 +03:00
Ilyas a9eac7edd2 fix(ldap): pass through LDAP mail attribute instead of crafting email (#834)
* fix(ldap): pass through LDAP mail attribute instead of crafting email

TinyAuth was constructing LDAP user emails as username@CookieDomain
instead of using the mail attribute stored in the directory. This caused
OIDC clients like Grafana to receive a synthetic email rather than the
real one.

Rename GetUserDN to GetUserInfo and extend it to also fetch the mail
attribute in the same LDAP query. Thread the result through UserSearch
and use it in both the login flow and the basic auth middleware, falling
back to the crafted email only when LDAP returns no mail value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add ldap email logic back after main merge

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Stavros <steveiliop56@gmail.com>
2026-05-11 15:40:15 +03:00
dependabot[bot] a6351790c3 chore(deps): bump github/codeql-action from 4.35.3 to 4.35.4 (#842)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.3 to 4.35.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/e46ed2cbd01164d986452f91f178727624ae40d7...68bde559dea0fdcac2102bfdf6230c5f70eb485e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 16:36:01 +03:00
35 changed files with 5427 additions and 1300 deletions
-38
View File
@@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help improve Tinyauth
title: "[BUG]"
labels: bug
assignees:
- steveiliop56
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
Please include the Tinyauth logs below, make sure to not include sensitive info.
**Device (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Tinyauth [e.g. v2.1.1]
- Docker [e.g. 27.3.1]
**
**Additional context**
Add any other context about the problem here.
+89
View File
@@ -0,0 +1,89 @@
name: Bug Report
description: Create a report to help us improve this project
title: "[BUG]"
labels: bug
assignees:
- steveiliop56
body:
- type: markdown
attributes:
value: |
Thanks for reporting a bug! Please provide detailed information below.
- type: textarea
id: description
attributes:
label: Describe the Bug
description: "A clear and concise description of what the bug is."
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: How to Reproduce
description: Steps to reproduce the behavior.
value: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: false
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: "A clear and concise description of what you expected to happen."
validations:
required: true
- type: textarea
id: context
attributes:
label: "Additional Context"
description: "If applicable add screenshots to help explain your problem."
validations:
required: false
- type: textarea
id: logs
attributes:
label: "Logs"
description: "Please include the Tinyauth logs, make sure to not include sensitive info."
validations:
required: false
- type: input
id: os
attributes:
label: Operating System
placeholder: "e.g. iOS, android, windows, linux, etc"
- type: input
id: browser
attributes:
label: Browser
placeholder: "e.g. chrome, firefox, safari, edge, etc"
- type: input
id: tinyauth
attributes:
label: Tinyauth Version
placeholder: "e.g. v5.0.0"
- type: input
id: docker
attributes:
label: Docker Version (if applicable)
placeholder: "e.g. 27.3.1"
- type: checkboxes
id: not-llm
attributes:
label: Human Written Confirmation
options:
- label: I confirm this issue was written by me and not generated by an LLM or AI assistant.
required: true
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Tinyauth Community Support on Discord
url: https://discord.gg/eHzVaCzRRd
about: Please ask and answer questions here.
- name: Tinyauth Documentation
url: https://tinyauth.app/docs/getting-started/
about: Please check the documentation here.
-21
View File
@@ -1,21 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: enhancement
assignees:
- steveiliop56
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
@@ -0,0 +1,52 @@
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: enhancement
assignees:
- steveiliop56
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a feature! Please provide detailed information below.
- type: textarea
id: problem
attributes:
label: Is your feature request related to a problem? Please describe.
description: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]"
validations:
required: false
- type: textarea
id: solution
attributes:
label: Describe the solution you'd like.
description: "A clear and concise description of what you want to happen."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered.
description: "A clear and concise description of any alternative solutions or features you've considered."
validations:
required: false
- type: textarea
id: context
attributes:
label: Additional context
description: "Add any other context or screenshots about the feature request here."
validations:
required: false
- type: checkboxes
id: not-llm
attributes:
label: Human Written Confirmation
options:
- label: I confirm this request was written by me and not generated by an LLM or AI assistant.
required: true
+1 -1
View File
@@ -1,6 +1,6 @@
version: 2
updates:
- package-ecosystem: "bun"
- package-ecosystem: "npm"
directory: "/frontend"
groups:
minor-patch:
+12 -15
View File
@@ -15,8 +15,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Setup pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
with:
package_json_file: ./frontend/package.json
- name: Setup go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -27,27 +29,22 @@ jobs:
run: go mod download
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
working-directory: ./frontend
run: pnpm ci
- name: Set version
run: |
echo testing > internal/assets/version
run: echo testing > internal/assets/version
- name: Lint frontend
run: |
cd frontend
bun run lint
working-directory: ./frontend
run: pnpm run lint
- name: Build frontend
run: |
cd frontend
bun run build
working-directory: ./frontend
run: pnpm run build
- name: Copy frontend
run: |
cp -r frontend/dist internal/assets/dist
run: cp -r frontend/dist internal/assets/dist
- name: Run tests
run: go test -coverprofile=coverage.txt -v ./...
+18 -20
View File
@@ -59,8 +59,10 @@ jobs:
with:
ref: nightly
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Setup pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
with:
package_json_file: ./frontend/package.json
- name: Install go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -68,18 +70,15 @@ jobs:
go-version: "^1.26.0"
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
working-directory: ./frontend
run: pnpm ci
- name: Install backend dependencies
run: |
go mod download
run: go mod download
- name: Build frontend
run: |
cd frontend
bun run build
working-directory: ./frontend
run: pnpm run build
- name: Build
run: |
@@ -105,8 +104,10 @@ jobs:
with:
ref: nightly
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Setup pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
with:
package_json_file: ./frontend/package.json
- name: Install go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -114,18 +115,15 @@ jobs:
go-version: "^1.26.0"
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
working-directory: ./frontend
run: pnpm ci
- name: Install backend dependencies
run: |
go mod download
run: go mod download
- name: Build frontend
run: |
cd frontend
bun run build
working-directory: ./frontend
run: pnpm run build
- name: Build
run: |
+18 -20
View File
@@ -35,8 +35,10 @@ jobs:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Setup pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
with:
package_json_file: ./frontend/package.json
- name: Install go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -44,18 +46,15 @@ jobs:
go-version: "^1.26.0"
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
working-directory: ./frontend
run: pnpm ci
- name: Install backend dependencies
run: |
go mod download
run: go mod download
- name: Build frontend
run: |
cd frontend
bun run build
working-directory: ./frontend
run: pnpm run build
- name: Build
run: |
@@ -78,8 +77,10 @@ jobs:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Setup pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
with:
package_json_file: ./frontend/package.json
- name: Install go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -87,18 +88,15 @@ jobs:
go-version: "^1.26.0"
- name: Install frontend dependencies
run: |
cd frontend
bun install --frozen-lockfile
working-directory: ./frontend
run: pnpm ci
- name: Install backend dependencies
run: |
go mod download
run: go mod download
- name: Build frontend
run: |
cd frontend
bun run build
working-directory: ./frontend
run: pnpm run build
- name: Build
run: |
+1 -1
View File
@@ -38,6 +38,6 @@ jobs:
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
with:
sarif_file: results.sarif
+3
View File
@@ -48,3 +48,6 @@ __debug_*
# testing config
config.certify.yml
# deepsec
/.deepsec
+2 -2
View File
@@ -7,7 +7,7 @@ Contributing to Tinyauth is straightforward. Follow the steps below to set up a
## Requirements
- Bun
- pnpm
- Golang v1.24.0 or later
- Git
- Docker
@@ -34,7 +34,7 @@ Frontend dependencies can be installed as follows:
```sh
cd frontend/
bun install
pnpm ci
```
## Create the `.env` file
+7 -5
View File
@@ -1,12 +1,14 @@
# Site builder
FROM oven/bun:1.3.13-alpine AS frontend-builder
FROM node:26.1-alpine3.23 AS frontend-builder
WORKDIR /frontend
COPY ./frontend/package.json ./
COPY ./frontend/bun.lock ./
RUN npm install -g pnpm@11.1.2
RUN bun install --frozen-lockfile
COPY ./frontend/package.json ./
COPY ./frontend/pnpm-lock.yaml ./
RUN pnpm ci
COPY ./frontend/public ./public
COPY ./frontend/src ./src
@@ -17,7 +19,7 @@ COPY ./frontend/tsconfig.app.json ./
COPY ./frontend/tsconfig.node.json ./
COPY ./frontend/vite.config.ts ./
RUN bun run build
RUN pnpm run build
# Builder
FROM golang:1.26-alpine3.23 AS builder
+1 -1
View File
@@ -8,7 +8,7 @@ COPY go.sum ./
RUN go mod download
RUN go install github.com/air-verse/air@v1.61.7
RUN go install github.com/go-delve/delve/cmd/dlv@latest
RUN go install github.com/go-delve/delve/cmd/dlv@v1.26.3
COPY ./cmd ./cmd
COPY ./internal ./internal
+7 -5
View File
@@ -1,12 +1,14 @@
# Site builder
FROM oven/bun:1.3.13-alpine AS frontend-builder
FROM node:26.1-alpine3.23 AS frontend-builder
WORKDIR /frontend
COPY ./frontend/package.json ./
COPY ./frontend/bun.lock ./
RUN npm install -g pnpm@11.1.2
RUN bun install --frozen-lockfile
COPY ./frontend/package.json ./
COPY ./frontend/pnpm-lock.yaml ./
RUN pnpm ci
COPY ./frontend/public ./public
COPY ./frontend/src ./src
@@ -17,7 +19,7 @@ COPY ./frontend/tsconfig.app.json ./
COPY ./frontend/tsconfig.node.json ./
COPY ./frontend/vite.config.ts ./
RUN bun run build
RUN pnpm run build
# Builder
FROM golang:1.26-alpine3.23 AS builder
+2 -2
View File
@@ -17,7 +17,7 @@ PROD_COMPOSE := $(shell test -f "docker-compose.test.prod.yml" && echo "docker-c
# Deps
deps:
bun install --frozen-lockfile --cwd frontend
cd frontend && pnpm ci
go mod download
# Clean data
@@ -31,7 +31,7 @@ clean-webui:
# Build the web UI
webui: clean-webui
bun run --cwd frontend build
cd frontend && pnpm run build
cp -r frontend/dist internal/assets
# Build the binary
+50 -2
View File
@@ -2,8 +2,56 @@
## Supported Versions
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.
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
Due to the nature of this app, it needs to be secure. If you discover any security issues or vulnerabilities in the app please contact me as soon as possible at <security@tinyauth.app>. Please do not use the issues section to report security issues as I won't be able to patch them in time and they may get exploited by malicious actors.
Please **do not** report security vulnerabilities through public GitHub issues, discussions, or pull requests as I won't be able to patch them in time and they may get exploited by malicious actors.
Instead, report them privately using [GitHub's Private Vulnerability Reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) via the **Security** tab of this repository.
Or send us an email at <security@tinyauth.app>.
### A note on AI-assisted reports
If AI tooling (LLMs, automated scanners, agentic assistants, etc.) helped you discover, analyse, or write up this issue, please say so in your report. This isn't a judgement - AI-assisted findings are welcome - but disclosing it up front helps maintainers calibrate how much additional verification a report needs, and tends to make the report itself clearer.
When submitting a report, please use the structure below so it can be triaged quickly.
---
### 1. Summary
A short, one-paragraph description of the vulnerability and its impact (e.g. what an attacker can achieve, who is affected, and under what conditions).
### 2. Steps to Reproduce / Proof of Concept
Provide a minimal, reliable reproduction:
1. Step one
2. Step two
3. Step three
Include any required input, payloads, configuration, or code snippets. Attach a PoC script or screenshots where helpful.
### 3. Expected vs. Actual Behaviour
- **Expected:** what *should* happen
- **Actual:** what *does* happen, and why it's a security issue
### 4. Suggested Fix or Mitigation *(optional)*
If you have an idea for how to address the issue, describe it here. A private gist link is welcome but not required.
- **Have you tested this fix?** Yes / No
- **If yes,** briefly describe how it was tested and what was verified.
---
## What to Expect
- **Acknowledgement** within a reasonable timeframe after receiving your report
- **Updates** as the issue is investigated and addressed
- **Public credit** in the resulting advisory, along with any **CVE assigned**, unless you'd prefer to stay anonymous
We follow a **90-day coordinated disclosure** window: please allow up to 90 days from the date of your report for the issue to be investigated and patched before publicly disclosing it. The publication date - whether earlier if a fix lands sooner, or later if more time is genuinely needed - will be agreed with you in advance.
-6
View File
@@ -1,6 +0,0 @@
# Ignore artifacts:
dist
node_modules
bun.lock
package.json
src/lib/i18n/locales
-1
View File
@@ -1 +0,0 @@
{}
+6 -4
View File
@@ -1,11 +1,13 @@
FROM oven/bun:1.2.16-alpine
FROM node:26.1-alpine3.23
RUN npm install -g pnpm@11.1.2
WORKDIR /frontend
COPY ./frontend/package.json ./
COPY ./frontend/bun.lock ./
COPY ./frontend/pnpm-lock.yaml ./
RUN bun install --frozen-lockfile
RUN pnpm ci
COPY ./frontend/public ./public
COPY ./frontend/src ./src
@@ -19,4 +21,4 @@ COPY ./frontend/vite.config.ts ./
EXPOSE 5173
ENTRYPOINT ["bun", "run", "dev"]
ENTRYPOINT ["pnpm", "run", "dev"]
-1107
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -10,6 +10,7 @@
"preview": "vite preview",
"tsc": "tsc -b"
},
"packageManager": "pnpm@11.1.2",
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-dropdown-menu": "^2.1.16",
+5072
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
dangerouslyAllowAllBuilds: false
blockExoticSubdeps: true
minimumReleaseAge: 1440 # 1 day
trustPolicy: no-downgrade
+6 -1
View File
@@ -208,7 +208,12 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
name = user.Name
} else {
controller.log.App.Debug().Msg("No name from OAuth provider, generating from email")
name = fmt.Sprintf("%s (%s)", utils.Capitalize(strings.Split(user.Email, "@")[0]), strings.Split(user.Email, "@")[1])
parts := strings.SplitN(user.Email, "@", 2)
if len(parts) == 2 {
name = fmt.Sprintf("%s (%s)", utils.Capitalize(parts[0]), parts[1])
} else {
name = utils.Capitalize(user.Email)
}
}
var username string
+2 -2
View File
@@ -146,7 +146,7 @@ func (controller *OIDCController) Authorize(c *gin.Context) {
client, ok := controller.oidc.GetClient(req.ClientID)
if !ok {
controller.authorizeError(c, err, "Client not found", "The client ID is invalid", "", "", "")
controller.authorizeError(c, fmt.Errorf("client not found: %s", req.ClientID), "Client not found", "The client ID is invalid", "", "", "")
return
}
@@ -288,7 +288,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
entry, err := controller.oidc.GetCodeEntry(c, controller.oidc.Hash(req.Code), client.ClientID)
if err != nil {
if err := controller.oidc.DeleteTokenByCodeHash(c, controller.oidc.Hash(req.Code)); err != nil {
controller.log.App.Error().Err(err).Msg("Failed to delete code")
controller.log.App.Error().Err(err).Msg("Failed to revoke tokens for replayed code")
}
if errors.Is(err, service.ErrCodeNotFound) {
controller.log.App.Warn().Msg("Code not found")
+3 -3
View File
@@ -144,9 +144,9 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
if !controller.useBrowserResponse(proxyCtx) {
c.Header("x-tinyauth-location", redirectURL)
c.JSON(401, gin.H{
"status": 401,
"message": "Unauthorized",
c.JSON(403, gin.H{
"status": 403,
"message": "Forbidden",
})
return
}
+1 -1
View File
@@ -32,7 +32,7 @@ func (controller *ResourcesController) resourcesHandler(c *gin.Context) {
if controller.config.Resources.Path == "" {
c.JSON(404, gin.H{
"status": 404,
"message": "Resources not found",
"message": "Resource not found",
})
return
}
+3
View File
@@ -189,6 +189,9 @@ func (controller *UserController) loginHandler(c *gin.Context) {
if search.Type == model.UserLDAP {
sessionCookie.Provider = "ldap"
if search.Email != "" {
sessionCookie.Email = search.Email
}
}
cookie, err := controller.auth.CreateSession(c, sessionCookie)
+10 -1
View File
@@ -160,7 +160,12 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string) (*model
userContext.LDAP.Groups = user.Groups
userContext.LDAP.Name = utils.Capitalize(userContext.LDAP.Username)
userContext.LDAP.Email = utils.CompileUserEmail(userContext.LDAP.Username, m.runtime.CookieDomain)
if search.Email != "" {
userContext.LDAP.Email = search.Email
}
case model.ProviderOAuth:
_, exists := m.broker.GetService(userContext.OAuth.ID)
@@ -238,11 +243,15 @@ func (m *ContextMiddleware) basicAuth(username string, password string) (*model.
BaseContext: model.BaseContext{
Username: username,
Name: utils.Capitalize(username),
Email: utils.CompileUserEmail(username, m.runtime.CookieDomain),
},
Groups: user.Groups,
}
userContext.Provider = model.ProviderLDAP
userContext.LDAP.Email = utils.CompileUserEmail(username, m.runtime.CookieDomain)
if search.Email != "" {
userContext.LDAP.Email = search.Email
}
}
userContext.Authenticated = true
+1
View File
@@ -21,5 +21,6 @@ type LocalUser struct {
type UserSearch struct {
Username string
Email string // used for LDAP, we can't throw it to LDAPUser because it would need another cache or an LDAP lookup every time
Type UserSearchType
}
+33 -27
View File
@@ -130,7 +130,7 @@ func (auth *AuthService) SearchUser(username string) (*model.UserSearch, error)
}
if auth.ldap != nil {
userDN, err := auth.ldap.GetUserDN(username)
userDN, email, err := auth.ldap.GetUserInfo(username)
if err != nil {
return nil, fmt.Errorf("failed to get ldap user: %w", err)
@@ -138,6 +138,7 @@ func (auth *AuthService) SearchUser(username string) (*model.UserSearch, error)
return &model.UserSearch{
Username: userDN,
Email: email,
Type: model.UserLDAP,
}, nil
}
@@ -772,46 +773,49 @@ func (auth *AuthService) ensureOAuthSessionLimit() {
auth.oauthMutex.Lock()
defer auth.oauthMutex.Unlock()
if len(auth.oauthPendingSessions) >= MaxOAuthPendingSessions {
if len(auth.oauthPendingSessions) <= MaxOAuthPendingSessions {
return
}
cleanupIds := make([]string, 0, OAuthCleanupCount)
for range OAuthCleanupCount {
oldestId := ""
oldestTime := int64(0)
type entry struct {
id string
expiresAt int64
}
entries := make([]entry, 0, len(auth.oauthPendingSessions))
for id, session := range auth.oauthPendingSessions {
if oldestTime == 0 {
oldestId = id
oldestTime = session.ExpiresAt.Unix()
continue
}
if slices.Contains(cleanupIds, id) {
continue
}
if session.ExpiresAt.Unix() < oldestTime {
oldestId = id
oldestTime = session.ExpiresAt.Unix()
}
entries = append(entries, entry{id, session.ExpiresAt.Unix()})
}
cleanupIds = append(cleanupIds, oldestId)
slices.SortFunc(entries, func(a, b entry) int {
if a.expiresAt < b.expiresAt {
return -1
}
if a.expiresAt > b.expiresAt {
return 1
}
return 0
})
for _, id := range cleanupIds {
delete(auth.oauthPendingSessions, id)
}
for _, e := range entries[:OAuthCleanupCount] {
delete(auth.oauthPendingSessions, e.id)
}
}
func (auth *AuthService) lockdownMode() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
auth.lockdownCtx = ctx
auth.lockdownCancelFunc = cancel
auth.loginMutex.Lock()
if auth.lockdown != nil && auth.lockdown.Active {
auth.loginMutex.Unlock()
cancel()
return
}
auth.lockdownCtx = ctx
auth.lockdownCancelFunc = cancel
auth.log.App.Warn().Msg("Too many failed login attempts, entering lockdown mode")
auth.lockdown = &Lockdown{
@@ -824,10 +828,12 @@ func (auth *AuthService) lockdownMode() {
auth.loginAttempts = make(map[string]*LoginAttempt)
timer := time.NewTimer(time.Until(auth.lockdown.ActiveUntil))
defer timer.Stop()
auth.loginMutex.Unlock()
defer cancel()
defer timer.Stop()
select {
case <-timer.C:
// Timer expired, end lockdown
+6 -7
View File
@@ -134,8 +134,7 @@ func (ldap *LdapService) connect() (*ldapgo.Conn, error) {
return ldap.conn, nil
}
func (ldap *LdapService) GetUserDN(username string) (string, error) {
// Escape the username to prevent LDAP injection
func (ldap *LdapService) GetUserInfo(username string) (dn string, email string, err error) {
escapedUsername := ldapgo.EscapeFilter(username)
filter := fmt.Sprintf(ldap.config.LDAP.SearchFilter, escapedUsername)
@@ -143,7 +142,7 @@ func (ldap *LdapService) GetUserDN(username string) (string, error) {
ldap.config.LDAP.BaseDN,
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
filter,
[]string{"dn"},
[]string{"dn", "mail"},
nil,
)
@@ -152,15 +151,15 @@ func (ldap *LdapService) GetUserDN(username string) (string, error) {
searchResult, err := ldap.conn.Search(searchRequest)
if err != nil {
return "", err
return "", "", err
}
if len(searchResult.Entries) != 1 {
return "", fmt.Errorf("multiple or no entries found for user %s", username)
return "", "", fmt.Errorf("multiple or no entries found for user %s", username)
}
userDN := searchResult.Entries[0].DN
return userDN, nil
entry := searchResult.Entries[0]
return entry.DN, entry.GetAttributeValue("mail"), nil
}
func (ldap *LdapService) GetUserGroups(userDN string) ([]string, error) {
+1
View File
@@ -26,6 +26,7 @@ func NewOAuthService(config model.OAuthServiceConfig, id string, ctx context.Con
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: config.Insecure,
MinVersion: tls.VersionTLS12,
},
},
}
+5 -5
View File
@@ -297,6 +297,11 @@ func (service *OIDCService) ValidateAuthorizeParams(req AuthorizeRequest) error
return errors.New("access_denied")
}
// Redirect URI to verify that it's trusted
if !slices.Contains(client.TrustedRedirectURIs, req.RedirectURI) {
return errors.New("invalid_request_uri")
}
// Scopes
scopes := strings.Split(req.Scope, " ")
@@ -318,11 +323,6 @@ func (service *OIDCService) ValidateAuthorizeParams(req AuthorizeRequest) error
return errors.New("unsupported_response_type")
}
// Redirect URI
if !slices.Contains(client.TrustedRedirectURIs, req.RedirectURI) {
return errors.New("invalid_request_uri")
}
// PKCE code challenge method if set
if req.CodeChallenge != "" && req.CodeChallengeMethod != "" {
if req.CodeChallengeMethod != "S256" && req.CodeChallengeMethod != "plain" {