Merge remote-tracking branch 'origin/master' into feat-settings-ui
commit
bd279900ca
|
@ -23,7 +23,13 @@
|
||||||
"maintenance",
|
"maintenance",
|
||||||
"question",
|
"question",
|
||||||
"review",
|
"review",
|
||||||
"test"
|
"test",
|
||||||
|
"mentoring",
|
||||||
|
"infra",
|
||||||
|
"design",
|
||||||
|
"userTesting",
|
||||||
|
"tool",
|
||||||
|
"research"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -38,7 +44,13 @@
|
||||||
"maintenance",
|
"maintenance",
|
||||||
"question",
|
"question",
|
||||||
"review",
|
"review",
|
||||||
"test"
|
"test",
|
||||||
|
"mentoring",
|
||||||
|
"infra",
|
||||||
|
"design",
|
||||||
|
"userTesting",
|
||||||
|
"tool",
|
||||||
|
"research"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -53,7 +65,13 @@
|
||||||
"maintenance",
|
"maintenance",
|
||||||
"question",
|
"question",
|
||||||
"review",
|
"review",
|
||||||
"test"
|
"test",
|
||||||
|
"mentoring",
|
||||||
|
"infra",
|
||||||
|
"design",
|
||||||
|
"userTesting",
|
||||||
|
"tool",
|
||||||
|
"research"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -649,7 +667,14 @@
|
||||||
"profile": "https://github.com/mind-ar",
|
"profile": "https://github.com/mind-ar",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code",
|
"code",
|
||||||
"translation"
|
"translation",
|
||||||
|
"doc",
|
||||||
|
"bug",
|
||||||
|
"design",
|
||||||
|
"test",
|
||||||
|
"review",
|
||||||
|
"research",
|
||||||
|
"ideas"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -834,6 +859,63 @@
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"doc"
|
"doc"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "chillinPanda",
|
||||||
|
"name": "Dinh Bao Dang",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/250694?v=4",
|
||||||
|
"profile": "https://github.com/chillinPanda",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "levkoburburas",
|
||||||
|
"name": "levkoburburas",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/62853952?v=4",
|
||||||
|
"profile": "https://github.com/levkoburburas",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"ideas",
|
||||||
|
"bug"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "tiuub",
|
||||||
|
"name": "tiuub",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/46517077?v=4",
|
||||||
|
"profile": "https://github.com/tiuub",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "joshgordon",
|
||||||
|
"name": "Josh Gordon",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/2125341?v=4",
|
||||||
|
"profile": "http://joshgordon.net",
|
||||||
|
"contributions": [
|
||||||
|
"ideas",
|
||||||
|
"security"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "silasfrancisco",
|
||||||
|
"name": "silasfrancisco",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/84447762?v=4",
|
||||||
|
"profile": "https://github.com/silasfrancisco",
|
||||||
|
"contributions": [
|
||||||
|
"security"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "n4m3l3ss-b0t",
|
||||||
|
"name": "Ricardo Pesqueira",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/1162710?v=4",
|
||||||
|
"profile": "https://github.com/n4m3l3ss-b0t",
|
||||||
|
"contributions": [
|
||||||
|
"security"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7
|
"contributorsPerLine": 7
|
||||||
|
|
|
@ -7,7 +7,7 @@ trim_trailing_whitespace = true
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.{sh,yml,yaml}]
|
[*.{html,sh,yml,yaml}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
"npm"
|
"npm"
|
||||||
],
|
],
|
||||||
"kubernetes": {
|
"kubernetes": {
|
||||||
"fileMatch": ["kube/.+\\.yml$"],
|
"fileMatch": ["kube/.+\\.yml$"]
|
||||||
},
|
},
|
||||||
"labels": [
|
"labels": [
|
||||||
"dependencies"
|
"dependencies"
|
||||||
|
@ -30,7 +30,6 @@
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
"matchUpdateTypes": ["digest", "minor", "patch"],
|
"matchUpdateTypes": ["digest", "minor", "patch"],
|
||||||
"matchCurrentVersion": "!/^0/",
|
|
||||||
"automerge": true,
|
"automerge": true,
|
||||||
"automergeType": "pr",
|
"automergeType": "pr",
|
||||||
"platformAutomerge": true
|
"platformAutomerge": true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
# ===================================
|
# ===================================
|
||||||
FROM alpine:3.17.0
|
FROM alpine:3.17.1
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
|
@ -15,7 +15,7 @@ RUN yarn global add pnpm && \
|
||||||
# =======================================
|
# =======================================
|
||||||
# ===== Build image for the backend =====
|
# ===== Build image for the backend =====
|
||||||
# =======================================
|
# =======================================
|
||||||
FROM golang:1.19.4-alpine AS builder-backend
|
FROM golang:1.19.5-alpine AS builder-backend
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ RUN \
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
# ===================================
|
# ===================================
|
||||||
FROM alpine:3.17.0
|
FROM alpine:3.17.1
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
RUN apk --no-cache add ca-certificates tzdata
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ RUN yarn install --frozen-lockfile && yarn build
|
||||||
# =======================================
|
# =======================================
|
||||||
# ===== Build image for the backend =====
|
# ===== Build image for the backend =====
|
||||||
# =======================================
|
# =======================================
|
||||||
FROM golang:1.19.4-alpine AS builder-backend
|
FROM golang:1.19.5-alpine AS builder-backend
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ RUN \
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
# ===================================
|
# ===================================
|
||||||
FROM alpine:3.17.0
|
FROM alpine:3.17.1
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
16
README.md
16
README.md
|
@ -197,9 +197,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/clems4ever"><img src="https://avatars.githubusercontent.com/u/3193257?v=4?s=100" width="100px;" alt="Clément Michaud"/><br /><sub><b>Clément Michaud</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=clems4ever" title="Code">💻</a> <a href="https://github.com/authelia/authelia/commits?author=clems4ever" title="Documentation">📖</a> <a href="#ideas-clems4ever" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-clems4ever" title="Maintenance">🚧</a> <a href="#question-clems4ever" title="Answering Questions">💬</a> <a href="https://github.com/authelia/authelia/pulls?q=is%3Apr+reviewed-by%3Aclems4ever" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/authelia/authelia/commits?author=clems4ever" title="Tests">⚠️</a></td>
|
<td align="center"><a href="https://github.com/clems4ever"><img src="https://avatars.githubusercontent.com/u/3193257?v=4?s=100" width="100px;" alt="Clément Michaud"/><br /><sub><b>Clément Michaud</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=clems4ever" title="Code">💻</a> <a href="https://github.com/authelia/authelia/commits?author=clems4ever" title="Documentation">📖</a> <a href="#ideas-clems4ever" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-clems4ever" title="Maintenance">🚧</a> <a href="#question-clems4ever" title="Answering Questions">💬</a> <a href="https://github.com/authelia/authelia/pulls?q=is%3Apr+reviewed-by%3Aclems4ever" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/authelia/authelia/commits?author=clems4ever" title="Tests">⚠️</a> <a href="#mentoring-clems4ever" title="Mentoring">🧑🏫</a> <a href="#infra-clems4ever" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#design-clems4ever" title="Design">🎨</a> <a href="#userTesting-clems4ever" title="User Testing">📓</a> <a href="#tool-clems4ever" title="Tools">🔧</a> <a href="#research-clems4ever" title="Research">🔬</a></td>
|
||||||
<td align="center"><a href="https://github.com/nightah"><img src="https://avatars.githubusercontent.com/u/3339418?v=4?s=100" width="100px;" alt="Amir Zarrinkafsh"/><br /><sub><b>Amir Zarrinkafsh</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=nightah" title="Code">💻</a> <a href="https://github.com/authelia/authelia/commits?author=nightah" title="Documentation">📖</a> <a href="#ideas-nightah" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-nightah" title="Maintenance">🚧</a> <a href="#question-nightah" title="Answering Questions">💬</a> <a href="https://github.com/authelia/authelia/pulls?q=is%3Apr+reviewed-by%3Anightah" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/authelia/authelia/commits?author=nightah" title="Tests">⚠️</a></td>
|
<td align="center"><a href="https://github.com/nightah"><img src="https://avatars.githubusercontent.com/u/3339418?v=4?s=100" width="100px;" alt="Amir Zarrinkafsh"/><br /><sub><b>Amir Zarrinkafsh</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=nightah" title="Code">💻</a> <a href="https://github.com/authelia/authelia/commits?author=nightah" title="Documentation">📖</a> <a href="#ideas-nightah" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-nightah" title="Maintenance">🚧</a> <a href="#question-nightah" title="Answering Questions">💬</a> <a href="https://github.com/authelia/authelia/pulls?q=is%3Apr+reviewed-by%3Anightah" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/authelia/authelia/commits?author=nightah" title="Tests">⚠️</a> <a href="#mentoring-nightah" title="Mentoring">🧑🏫</a> <a href="#infra-nightah" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#design-nightah" title="Design">🎨</a> <a href="#userTesting-nightah" title="User Testing">📓</a> <a href="#tool-nightah" title="Tools">🔧</a> <a href="#research-nightah" title="Research">🔬</a></td>
|
||||||
<td align="center"><a href="https://github.com/james-d-elliott"><img src="https://avatars.githubusercontent.com/u/3903683?v=4?s=100" width="100px;" alt="James Elliott"/><br /><sub><b>James Elliott</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=james-d-elliott" title="Code">💻</a> <a href="https://github.com/authelia/authelia/commits?author=james-d-elliott" title="Documentation">📖</a> <a href="#ideas-james-d-elliott" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-james-d-elliott" title="Maintenance">🚧</a> <a href="#question-james-d-elliott" title="Answering Questions">💬</a> <a href="https://github.com/authelia/authelia/pulls?q=is%3Apr+reviewed-by%3Ajames-d-elliott" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/authelia/authelia/commits?author=james-d-elliott" title="Tests">⚠️</a></td>
|
<td align="center"><a href="https://github.com/james-d-elliott"><img src="https://avatars.githubusercontent.com/u/3903683?v=4?s=100" width="100px;" alt="James Elliott"/><br /><sub><b>James Elliott</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=james-d-elliott" title="Code">💻</a> <a href="https://github.com/authelia/authelia/commits?author=james-d-elliott" title="Documentation">📖</a> <a href="#ideas-james-d-elliott" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-james-d-elliott" title="Maintenance">🚧</a> <a href="#question-james-d-elliott" title="Answering Questions">💬</a> <a href="https://github.com/authelia/authelia/pulls?q=is%3Apr+reviewed-by%3Ajames-d-elliott" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/authelia/authelia/commits?author=james-d-elliott" title="Tests">⚠️</a> <a href="#mentoring-james-d-elliott" title="Mentoring">🧑🏫</a> <a href="#infra-james-d-elliott" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#design-james-d-elliott" title="Design">🎨</a> <a href="#userTesting-james-d-elliott" title="User Testing">📓</a> <a href="#tool-james-d-elliott" title="Tools">🔧</a> <a href="#research-james-d-elliott" title="Research">🔬</a></td>
|
||||||
<td align="center"><a href="https://github.com/n4kre"><img src="https://avatars.githubusercontent.com/u/14371127?v=4?s=100" width="100px;" alt="Antoine Favre"/><br /><sub><b>Antoine Favre</b></sub></a><br /><a href="https://github.com/authelia/authelia/issues?q=author%3An4kre" title="Bug reports">🐛</a> <a href="#ideas-n4kre" title="Ideas, Planning, & Feedback">🤔</a></td>
|
<td align="center"><a href="https://github.com/n4kre"><img src="https://avatars.githubusercontent.com/u/14371127?v=4?s=100" width="100px;" alt="Antoine Favre"/><br /><sub><b>Antoine Favre</b></sub></a><br /><a href="https://github.com/authelia/authelia/issues?q=author%3An4kre" title="Bug reports">🐛</a> <a href="#ideas-n4kre" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||||
<td align="center"><a href="https://github.com/BankaiNoJutsu"><img src="https://avatars.githubusercontent.com/u/2241519?v=4?s=100" width="100px;" alt="BankaiNoJutsu"/><br /><sub><b>BankaiNoJutsu</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=BankaiNoJutsu" title="Code">💻</a> <a href="#design-BankaiNoJutsu" title="Design">🎨</a></td>
|
<td align="center"><a href="https://github.com/BankaiNoJutsu"><img src="https://avatars.githubusercontent.com/u/2241519?v=4?s=100" width="100px;" alt="BankaiNoJutsu"/><br /><sub><b>BankaiNoJutsu</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=BankaiNoJutsu" title="Code">💻</a> <a href="#design-BankaiNoJutsu" title="Design">🎨</a></td>
|
||||||
<td align="center"><a href="https://github.com/p-rintz"><img src="https://avatars.githubusercontent.com/u/13933258?v=4?s=100" width="100px;" alt="Philipp Rintz"/><br /><sub><b>Philipp Rintz</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=p-rintz" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/p-rintz"><img src="https://avatars.githubusercontent.com/u/13933258?v=4?s=100" width="100px;" alt="Philipp Rintz"/><br /><sub><b>Philipp Rintz</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=p-rintz" title="Documentation">📖</a></td>
|
||||||
|
@ -281,7 +281,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||||
<td align="center"><a href="http://www.dennisgaida.de"><img src="https://avatars.githubusercontent.com/u/2392217?v=4?s=100" width="100px;" alt="Dennis Gaida"/><br /><sub><b>Dennis Gaida</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=DennisGaida" title="Documentation">📖</a></td>
|
<td align="center"><a href="http://www.dennisgaida.de"><img src="https://avatars.githubusercontent.com/u/2392217?v=4?s=100" width="100px;" alt="Dennis Gaida"/><br /><sub><b>Dennis Gaida</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=DennisGaida" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/Alestrix"><img src="https://avatars.githubusercontent.com/u/7452860?v=4?s=100" width="100px;" alt="Alestrix"/><br /><sub><b>Alestrix</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=Alestrix" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/Alestrix"><img src="https://avatars.githubusercontent.com/u/7452860?v=4?s=100" width="100px;" alt="Alestrix"/><br /><sub><b>Alestrix</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=Alestrix" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/bgh-github"><img src="https://avatars.githubusercontent.com/u/99472455?v=4?s=100" width="100px;" alt="bgh-github"/><br /><sub><b>bgh-github</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=bgh-github" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/bgh-github"><img src="https://avatars.githubusercontent.com/u/99472455?v=4?s=100" width="100px;" alt="bgh-github"/><br /><sub><b>bgh-github</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=bgh-github" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/mind-ar"><img src="https://avatars.githubusercontent.com/u/10672208?v=4?s=100" width="100px;" alt="Manuel Nuñez"/><br /><sub><b>Manuel Nuñez</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=mind-ar" title="Code">💻</a> <a href="#translation-mind-ar" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/mind-ar"><img src="https://avatars.githubusercontent.com/u/10672208?v=4?s=100" width="100px;" alt="Manuel Nuñez"/><br /><sub><b>Manuel Nuñez</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=mind-ar" title="Code">💻</a> <a href="#translation-mind-ar" title="Translation">🌍</a> <a href="https://github.com/authelia/authelia/commits?author=mind-ar" title="Documentation">📖</a> <a href="https://github.com/authelia/authelia/issues?q=author%3Amind-ar" title="Bug reports">🐛</a> <a href="#design-mind-ar" title="Design">🎨</a> <a href="https://github.com/authelia/authelia/commits?author=mind-ar" title="Tests">⚠️</a> <a href="https://github.com/authelia/authelia/pulls?q=is%3Apr+reviewed-by%3Amind-ar" title="Reviewed Pull Requests">👀</a> <a href="#research-mind-ar" title="Research">🔬</a> <a href="#ideas-mind-ar" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||||
<td align="center"><a href="https://github.com/protvis74"><img src="https://avatars.githubusercontent.com/u/50554836?v=4?s=100" width="100px;" alt="protvis74"/><br /><sub><b>protvis74</b></sub></a><br /><a href="#translation-protvis74" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/protvis74"><img src="https://avatars.githubusercontent.com/u/50554836?v=4?s=100" width="100px;" alt="protvis74"/><br /><sub><b>protvis74</b></sub></a><br /><a href="#translation-protvis74" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="http://itjamie.com"><img src="https://avatars.githubusercontent.com/u/1613241?v=4?s=100" width="100px;" alt="Jamie (Bear) Murphy "/><br /><sub><b>Jamie (Bear) Murphy </b></sub></a><br /><a href="https://github.com/authelia/authelia/pulls?q=is%3Apr+reviewed-by%3AITJamie" title="Reviewed Pull Requests">👀</a></td>
|
<td align="center"><a href="http://itjamie.com"><img src="https://avatars.githubusercontent.com/u/1613241?v=4?s=100" width="100px;" alt="Jamie (Bear) Murphy "/><br /><sub><b>Jamie (Bear) Murphy </b></sub></a><br /><a href="https://github.com/authelia/authelia/pulls?q=is%3Apr+reviewed-by%3AITJamie" title="Reviewed Pull Requests">👀</a></td>
|
||||||
<td align="center"><a href="https://github.com/Beanow"><img src="https://avatars.githubusercontent.com/u/497556?v=4?s=100" width="100px;" alt="Robin van Boven"/><br /><sub><b>Robin van Boven</b></sub></a><br /><a href="#security-Beanow" title="Security">🛡️</a></td>
|
<td align="center"><a href="https://github.com/Beanow"><img src="https://avatars.githubusercontent.com/u/497556?v=4?s=100" width="100px;" alt="Robin van Boven"/><br /><sub><b>Robin van Boven</b></sub></a><br /><a href="#security-Beanow" title="Security">🛡️</a></td>
|
||||||
|
@ -308,6 +308,14 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||||
<td align="center"><a href="https://github.com/paul-ohl"><img src="https://avatars.githubusercontent.com/u/37795294?v=4?s=100" width="100px;" alt="Paul Ohl"/><br /><sub><b>Paul Ohl</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=paul-ohl" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/paul-ohl"><img src="https://avatars.githubusercontent.com/u/37795294?v=4?s=100" width="100px;" alt="Paul Ohl"/><br /><sub><b>Paul Ohl</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=paul-ohl" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/smkent"><img src="https://avatars.githubusercontent.com/u/2831985?v=4?s=100" width="100px;" alt="Stephen Kent"/><br /><sub><b>Stephen Kent</b></sub></a><br /><a href="#ideas-smkent" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/authelia/authelia/commits?author=smkent" title="Code">💻</a> <a href="#design-smkent" title="Design">🎨</a></td>
|
<td align="center"><a href="https://github.com/smkent"><img src="https://avatars.githubusercontent.com/u/2831985?v=4?s=100" width="100px;" alt="Stephen Kent"/><br /><sub><b>Stephen Kent</b></sub></a><br /><a href="#ideas-smkent" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/authelia/authelia/commits?author=smkent" title="Code">💻</a> <a href="#design-smkent" title="Design">🎨</a></td>
|
||||||
<td align="center"><a href="https://github.com/Ohelig"><img src="https://avatars.githubusercontent.com/u/5841980?v=4?s=100" width="100px;" alt="Ohelig"/><br /><sub><b>Ohelig</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=Ohelig" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/Ohelig"><img src="https://avatars.githubusercontent.com/u/5841980?v=4?s=100" width="100px;" alt="Ohelig"/><br /><sub><b>Ohelig</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=Ohelig" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/chillinPanda"><img src="https://avatars.githubusercontent.com/u/250694?v=4?s=100" width="100px;" alt="Dinh Bao Dang"/><br /><sub><b>Dinh Bao Dang</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=chillinPanda" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/levkoburburas"><img src="https://avatars.githubusercontent.com/u/62853952?v=4?s=100" width="100px;" alt="levkoburburas"/><br /><sub><b>levkoburburas</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=levkoburburas" title="Code">💻</a> <a href="#ideas-levkoburburas" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/authelia/authelia/issues?q=author%3Alevkoburburas" title="Bug reports">🐛</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/tiuub"><img src="https://avatars.githubusercontent.com/u/46517077?v=4?s=100" width="100px;" alt="tiuub"/><br /><sub><b>tiuub</b></sub></a><br /><a href="https://github.com/authelia/authelia/commits?author=tiuub" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="http://joshgordon.net"><img src="https://avatars.githubusercontent.com/u/2125341?v=4?s=100" width="100px;" alt="Josh Gordon"/><br /><sub><b>Josh Gordon</b></sub></a><br /><a href="#ideas-joshgordon" title="Ideas, Planning, & Feedback">🤔</a> <a href="#security-joshgordon" title="Security">🛡️</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/silasfrancisco"><img src="https://avatars.githubusercontent.com/u/84447762?v=4?s=100" width="100px;" alt="silasfrancisco"/><br /><sub><b>silasfrancisco</b></sub></a><br /><a href="#security-silasfrancisco" title="Security">🛡️</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/n4m3l3ss-b0t"><img src="https://avatars.githubusercontent.com/u/1162710?v=4?s=100" width="100px;" alt="Ricardo Pesqueira"/><br /><sub><b>Ricardo Pesqueira</b></sub></a><br /><a href="#security-n4m3l3ss-b0t" title="Security">🛡️</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -79,7 +79,7 @@ const (
|
||||||
|
|
||||||
type labelPriority int
|
type labelPriority int
|
||||||
|
|
||||||
//nolint:deadcode // Kept for future use.
|
//nolint:deadcode,varcheck // Kept for future use.
|
||||||
const (
|
const (
|
||||||
labelPriorityCritical labelPriority = iota
|
labelPriorityCritical labelPriority = iota
|
||||||
labelPriorityHigh
|
labelPriorityHigh
|
||||||
|
@ -122,7 +122,7 @@ func (s labelStatus) String() string {
|
||||||
|
|
||||||
type labelType int
|
type labelType int
|
||||||
|
|
||||||
//nolint:deadcode // Kept for future use.
|
//nolint:deadcode,varcheck // Kept for future use.
|
||||||
const (
|
const (
|
||||||
labelTypeFeature labelType = iota
|
labelTypeFeature labelType = iota
|
||||||
labelTypeBugUnconfirmed
|
labelTypeBugUnconfirmed
|
||||||
|
|
|
@ -114,6 +114,30 @@ var hostEntries = []HostEntry{
|
||||||
{Domain: "redis-sentinel-0.example.com", IP: "192.168.240.120"},
|
{Domain: "redis-sentinel-0.example.com", IP: "192.168.240.120"},
|
||||||
{Domain: "redis-sentinel-1.example.com", IP: "192.168.240.121"},
|
{Domain: "redis-sentinel-1.example.com", IP: "192.168.240.121"},
|
||||||
{Domain: "redis-sentinel-2.example.com", IP: "192.168.240.122"},
|
{Domain: "redis-sentinel-2.example.com", IP: "192.168.240.122"},
|
||||||
|
|
||||||
|
// For multi cookie domain tests.
|
||||||
|
{Domain: "login.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "admin.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "singlefactor.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "dev.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "home.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "mx1.mail.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "mx2.mail.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "public.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "secure.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "mail.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "duo.example2.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "login.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "admin.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "singlefactor.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "dev.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "home.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "mx1.mail.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "mx2.mail.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "public.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "secure.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "mail.example3.com", IP: "192.168.240.100"},
|
||||||
|
{Domain: "duo.example3.com", IP: "192.168.240.100"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCommand(cmd string, args ...string) {
|
func runCommand(cmd string, args ...string) {
|
||||||
|
|
|
@ -662,38 +662,76 @@ access_control:
|
||||||
## The session cookies identify the user once logged in.
|
## The session cookies identify the user once logged in.
|
||||||
## The available providers are: `memory`, `redis`. Memory is the provider unless redis is defined.
|
## The available providers are: `memory`, `redis`. Memory is the provider unless redis is defined.
|
||||||
session:
|
session:
|
||||||
|
## The secret to encrypt the session data. This is only used with Redis / Redis Sentinel.
|
||||||
|
## Secret can also be set using a secret: https://www.authelia.com/c/secrets
|
||||||
|
secret: 'insecure_session_secret'
|
||||||
|
|
||||||
|
## Cookies configures the list of allowed cookie domains for sessions to be created on.
|
||||||
|
## Undefined values will default to the values below.
|
||||||
|
# cookies:
|
||||||
|
# -
|
||||||
## The name of the session cookie.
|
## The name of the session cookie.
|
||||||
name: authelia_session
|
# name: 'authelia_session'
|
||||||
|
|
||||||
## The domain to protect.
|
## The domain to protect.
|
||||||
## Note: the authenticator must also be in that domain.
|
## Note: the Authelia portal must also be in that domain.
|
||||||
## If empty, the cookie is restricted to the subdomain of the issuer.
|
# domain: 'example.com'
|
||||||
domain: example.com
|
|
||||||
|
## Optional. The fully qualified URI of the portal to redirect users to on proxies that support redirections.
|
||||||
|
## Rules:
|
||||||
|
## - MUST use the secure scheme 'https://'
|
||||||
|
## - The above domain MUST either:
|
||||||
|
## - Match the host portion of this URI.
|
||||||
|
## - Match the suffix of the host portion when prefixed with '.'.
|
||||||
|
# authelia_url: 'https://auth.example.com'
|
||||||
|
|
||||||
## Sets the Cookie SameSite value. Possible options are none, lax, or strict.
|
## Sets the Cookie SameSite value. Possible options are none, lax, or strict.
|
||||||
## Please read https://www.authelia.com/c/session#same_site
|
## Please read https://www.authelia.com/c/session#same_site
|
||||||
same_site: lax
|
# same_site: 'lax'
|
||||||
|
|
||||||
## The secret to encrypt the session data. This is only used with Redis / Redis Sentinel.
|
## The value for inactivity, expiration, and remember_me are in seconds or the duration notation format.
|
||||||
## Secret can also be set using a secret: https://www.authelia.com/c/secrets
|
|
||||||
secret: insecure_session_secret
|
|
||||||
|
|
||||||
## The value for expiration, inactivity, and remember_me_duration are in seconds or the duration notation format.
|
|
||||||
## See: https://www.authelia.com/c/common#duration-notation-format
|
## See: https://www.authelia.com/c/common#duration-notation-format
|
||||||
## All three of these values affect the cookie/session validity period. Longer periods are considered less secure
|
## All three of these values affect the cookie/session validity period. Longer periods are considered less secure
|
||||||
## because a stolen cookie will last longer giving attackers more time to spy or attack.
|
## because a stolen cookie will last longer giving attackers more time to spy or attack.
|
||||||
|
|
||||||
## The time before the cookie expires and the session is destroyed if remember me IS NOT selected.
|
|
||||||
expiration: 1h
|
|
||||||
|
|
||||||
## The inactivity time before the session is reset. If expiration is set to 1h, and this is set to 5m, if the user
|
## The inactivity time before the session is reset. If expiration is set to 1h, and this is set to 5m, if the user
|
||||||
## does not select the remember me option their session will get destroyed after 1h, or after 5m since the last time
|
## does not select the remember me option their session will get destroyed after 1h, or after 5m since the last
|
||||||
## Authelia detected user activity.
|
## time Authelia detected user activity.
|
||||||
inactivity: 5m
|
# inactivity: '5m'
|
||||||
|
|
||||||
## The time before the cookie expires and the session is destroyed if remember me IS selected.
|
## The time before the session cookie expires and the session is destroyed if remember me IS NOT selected by the
|
||||||
## Value of -1 disables remember me.
|
## user.
|
||||||
remember_me_duration: 1M
|
# expiration: '1h'
|
||||||
|
|
||||||
|
## The time before the cookie expires and the session is destroyed if remember me IS selected by the user. Setting
|
||||||
|
## this value to -1 disables remember me for this session cookie domain.
|
||||||
|
# remember_me: '1M'
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'name' value. The name of the session cookie.
|
||||||
|
name: 'authelia_session'
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'same_site' value. Sets the Cookie SameSite value. Possible options are none, lax,
|
||||||
|
## or strict. Please read https://www.authelia.com/c/session#same_site
|
||||||
|
same_site: 'lax'
|
||||||
|
|
||||||
|
## The value for inactivity, expiration, and remember_me are in seconds or the duration notation format.
|
||||||
|
## See: https://www.authelia.com/c/common#duration-notation-format
|
||||||
|
## All three of these values affect the cookie/session validity period. Longer periods are considered less secure
|
||||||
|
## because a stolen cookie will last longer giving attackers more time to spy or attack.
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'inactivity' value. The inactivity time before the session is reset. If expiration is
|
||||||
|
## set to 1h, and this is set to 5m, if the user does not select the remember me option their session will get
|
||||||
|
## destroyed after 1h, or after 5m since the last time Authelia detected user activity.
|
||||||
|
inactivity: '5m'
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'expiration' value. The time before the session cookie expires and the session is
|
||||||
|
## destroyed if remember me IS NOT selected by the user.
|
||||||
|
expiration: '1h'
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'remember_me' value. The time before the cookie expires and the session is destroyed
|
||||||
|
## if remember me IS selected by the user. Setting this value to -1 disables remember me for all session cookie
|
||||||
|
## domains which do not have a specific 'remember_me' value.
|
||||||
|
remember_me: '1M'
|
||||||
|
|
||||||
##
|
##
|
||||||
## Redis Provider
|
## Redis Provider
|
||||||
|
|
|
@ -5,5 +5,5 @@ Please:
|
||||||
- Only edit the `/content` folder via the main Authelia [monorepo](https://github.com/authelia/authelia/tree/master/docs)
|
- Only edit the `/content` folder via the main Authelia [monorepo](https://github.com/authelia/authelia/tree/master/docs)
|
||||||
- Edit everything else via the Authelia [website repo](https://github.com/authelia/website)
|
- Edit everything else via the Authelia [website repo](https://github.com/authelia/website)
|
||||||
|
|
||||||
See the [Documentation Contributing Guide](https://www.authelia.com/contributing/prologue/documentation/) for more
|
See the [Documentation Contributing Guide](https://www.authelia.com/contributing/prologue/documentation-contributions/) for more
|
||||||
information.
|
information.
|
||||||
|
|
|
@ -150,7 +150,7 @@ filesystem but before parsing their content. These filters are _**NOT**_ covered
|
||||||
argument or environment variable will change and usage of these will either break or just not work.
|
argument or environment variable will change and usage of these will either break or just not work.
|
||||||
|
|
||||||
The filters are configured as a list of filter names by the `--config.experimental.filters` CLI argument and
|
The filters are configured as a list of filter names by the `--config.experimental.filters` CLI argument and
|
||||||
`X_AUTHELIA_CONFIG_EXPERIMENTAL_FILTERS` environment variable. We recommend using the environment variable as it ensures
|
`X_AUTHELIA_CONFIG_FILTERS` environment variable. We recommend using the environment variable as it ensures
|
||||||
commands executed from the container use the same filters. If both the CLI argument and environment variable are used
|
commands executed from the container use the same filters. If both the CLI argument and environment variable are used
|
||||||
the environment variable is completely ignored.
|
the environment variable is completely ignored.
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ authelia --config config.yml --config.experimental.filters expand-env,template
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text
|
||||||
X_AUTHELIA_CONFIG_EXPERIMENTAL_FILTERS=expand-env,template
|
X_AUTHELIA_CONFIG_FILTERS=expand-env,template
|
||||||
```
|
```
|
||||||
|
|
||||||
### Expand Environment Variable Filter
|
### Expand Environment Variable Filter
|
||||||
|
|
|
@ -22,7 +22,7 @@ describes the implementation of this. You can use this implementation in various
|
||||||
* session:
|
* session:
|
||||||
* expiration
|
* expiration
|
||||||
* inactivity
|
* inactivity
|
||||||
* remember_me_duration
|
* remember_me
|
||||||
* regulation:
|
* regulation:
|
||||||
* ban_time
|
* ban_time
|
||||||
* find_time
|
* find_time
|
||||||
|
|
|
@ -198,7 +198,7 @@ When used in conjunction with [domain] the rule will match when either the [doma
|
||||||
|
|
||||||
In addition to standard regex patterns this criteria can match some [Named Regex Groups].
|
In addition to standard regex patterns this criteria can match some [Named Regex Groups].
|
||||||
|
|
||||||
[domain_regex]: #domainregex
|
[domain_regex]: #domain_regex
|
||||||
|
|
||||||
##### Examples
|
##### Examples
|
||||||
|
|
||||||
|
@ -339,7 +339,7 @@ access_control:
|
||||||
{{< confkey type="list(string)" required="no" >}}
|
{{< confkey type="list(string)" required="no" >}}
|
||||||
|
|
||||||
This criteria is a list of values which can be an IP Address, network address range in CIDR notation, or an alias from
|
This criteria is a list of values which can be an IP Address, network address range in CIDR notation, or an alias from
|
||||||
the [global](#networks--global-) section. It matches against the first address in the `X-Forwarded-For` header, or if there
|
the [global](#networks-global) section. It matches against the first address in the `X-Forwarded-For` header, or if there
|
||||||
are none it will fall back to the IP address of the packet TCP source IP address. For this reason it's important for you
|
are none it will fall back to the IP address of the packet TCP source IP address. For this reason it's important for you
|
||||||
to configure the proxy server correctly in order to accurately match requests with this criteria. *__Note:__ you may
|
to configure the proxy server correctly in order to accurately match requests with this criteria. *__Note:__ you may
|
||||||
combine CIDR networks with the alias rules as you please.*
|
combine CIDR networks with the alias rules as you please.*
|
||||||
|
@ -360,7 +360,7 @@ for administrators to tune the security to their specific needs if desired.
|
||||||
|
|
||||||
##### Examples
|
##### Examples
|
||||||
|
|
||||||
*Require [two_factor](#twofactor) for all clients other than internal clients and `112.134.145.167`. The first two
|
*Require [two_factor](#two_factor) for all clients other than internal clients and `112.134.145.167`. The first two
|
||||||
rules in this list are effectively the same rule just expressed in different ways.*
|
rules in this list are effectively the same rule just expressed in different ways.*
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -485,7 +485,7 @@ access_control:
|
||||||
## Policies
|
## Policies
|
||||||
|
|
||||||
The policy of the first matching rule in the configured list decides the policy applied to the request, if no rule
|
The policy of the first matching rule in the configured list decides the policy applied to the request, if no rule
|
||||||
matches the request the [default_policy](#defaultpolicy) is applied.
|
matches the request the [default_policy](#default_policy) is applied.
|
||||||
|
|
||||||
[policies]: #policies
|
[policies]: #policies
|
||||||
|
|
||||||
|
@ -510,14 +510,14 @@ about the subject is [one_factor]. See [Rule Matching Concept 2] for more inform
|
||||||
This policy requires the user at minimum complete 1FA successfully (username and password). This means if they have
|
This policy requires the user at minimum complete 1FA successfully (username and password). This means if they have
|
||||||
performed 2FA then they will be allowed to access the resource.
|
performed 2FA then they will be allowed to access the resource.
|
||||||
|
|
||||||
[one_factor]: #onefactor
|
[one_factor]: #one_factor
|
||||||
|
|
||||||
### two_factor
|
### two_factor
|
||||||
|
|
||||||
This policy requires the user to complete 2FA successfully. This is currently the highest level of authentication
|
This policy requires the user to complete 2FA successfully. This is currently the highest level of authentication
|
||||||
policy available.
|
policy available.
|
||||||
|
|
||||||
[two_factor]: #twofactor
|
[two_factor]: #two_factor
|
||||||
|
|
||||||
## Rule Matching
|
## Rule Matching
|
||||||
|
|
||||||
|
@ -554,7 +554,7 @@ a match for that request.
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
```
|
```
|
||||||
|
|
||||||
[Rule Matching Concept 1]: #rule-matching-concept-1--sequential-order
|
[Rule Matching Concept 1]: #rule-matching-concept-1-sequential-order
|
||||||
|
|
||||||
### Rule Matching Concept 2: Subject Criteria Requires Authentication
|
### Rule Matching Concept 2: Subject Criteria Requires Authentication
|
||||||
|
|
||||||
|
@ -569,7 +569,7 @@ for authentication if no prior rules match the request per [Rule Matching Concep
|
||||||
identical rules, and one of them has a subject based reliant criteria, and the other one is a [bypass] rule then the
|
identical rules, and one of them has a subject based reliant criteria, and the other one is a [bypass] rule then the
|
||||||
[bypass] rule should generally come first.
|
[bypass] rule should generally come first.
|
||||||
|
|
||||||
[Rule Matching Concept 2]: #rule-matching-concept-2--subject-criteria-requires-authentication
|
[Rule Matching Concept 2]: #rule-matching-concept-2-subject-criteria-requires-authentication
|
||||||
|
|
||||||
## Named Regex Groups
|
## Named Regex Groups
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,21 @@ authenticated user and can then order the reverse proxy to let the request pass
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
session:
|
session:
|
||||||
|
secret: insecure_session_secret
|
||||||
|
|
||||||
name: authelia_session
|
name: authelia_session
|
||||||
|
same_site: lax
|
||||||
|
inactivity: 5m
|
||||||
|
expiration: 1h
|
||||||
|
remember_me: 1M
|
||||||
|
|
||||||
|
cookies:
|
||||||
|
- name: authelia_session
|
||||||
domain: example.com
|
domain: example.com
|
||||||
same_site: lax
|
same_site: lax
|
||||||
secret: unsecure_session_secret
|
|
||||||
expiration: 1h
|
|
||||||
inactivity: 5m
|
inactivity: 5m
|
||||||
remember_me_duration: 1M
|
expiration: 1h
|
||||||
|
remember_me: 1d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Providers
|
## Providers
|
||||||
|
@ -50,34 +58,6 @@ providers are recommended.
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
### name
|
|
||||||
|
|
||||||
{{< confkey type="string" default="authelia_session" required="no" >}}
|
|
||||||
|
|
||||||
The name of the session cookie. By default this is set to authelia_session. It's mostly useful to change this if you are
|
|
||||||
doing development or running multiple instances of Authelia.
|
|
||||||
|
|
||||||
### domain
|
|
||||||
|
|
||||||
{{< confkey type="string" required="yes" >}}
|
|
||||||
|
|
||||||
The domain the cookie is assigned to protect. This must be the same as the domain Authelia is served on or the root
|
|
||||||
of the domain. For example if listening on auth.example.com the cookie should be auth.example.com or example.com.
|
|
||||||
|
|
||||||
### same_site
|
|
||||||
|
|
||||||
{{< confkey type="string" default="lax" required="no" >}}
|
|
||||||
|
|
||||||
Sets the cookies SameSite value. Prior to offering the configuration choice this defaulted to None. The new default is
|
|
||||||
Lax. This option is defined in lower-case. So for example if you want to set it to `Strict`, the value in configuration
|
|
||||||
needs to be `strict`.
|
|
||||||
|
|
||||||
You can read about the SameSite cookie in detail on the
|
|
||||||
[MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite). In short setting SameSite to Lax
|
|
||||||
is generally the most desirable option for Authelia. None is not recommended unless you absolutely know what you're
|
|
||||||
doing and trust all the protected apps. Strict is not going to work in many use cases and we have not tested it in this
|
|
||||||
state but it's available as an option anyway.
|
|
||||||
|
|
||||||
### secret
|
### secret
|
||||||
|
|
||||||
{{< confkey type="string" required="yes" >}}
|
{{< confkey type="string" required="yes" >}}
|
||||||
|
@ -91,15 +71,29 @@ It's __strongly recommended__ this is a
|
||||||
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
|
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
|
||||||
characters.
|
characters.
|
||||||
|
|
||||||
### expiration
|
### domain
|
||||||
|
|
||||||
{{< confkey type="duration" default="1h" required="no" >}}
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
||||||
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
_**Deprecation Notice:** This option is deprecated. See the [cookies](#cookies) section instead._
|
||||||
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
|
||||||
|
|
||||||
The period of time before the cookie expires and the session is destroyed. This is overriden by
|
The domain the cookie is assigned to protect. This must be the same as the domain Authelia is served on or the root
|
||||||
[remember_me_duration](#remembermeduration) when the remember me box is checked.
|
of the domain. For example if listening on auth.example.com the cookie should be auth.example.com or example.com.
|
||||||
|
|
||||||
|
This value automatically maps to a single cookies configuration using the default values. It cannot be assigned at the
|
||||||
|
same time as a `cookies` configuration.
|
||||||
|
|
||||||
|
### name
|
||||||
|
|
||||||
|
{{< confkey type="string" default="authelia_session" required="no" >}}
|
||||||
|
|
||||||
|
The default `name` value for all [cookies](#cookies) configurations.
|
||||||
|
|
||||||
|
### same_site
|
||||||
|
|
||||||
|
{{< confkey type="string" default="lax" required="no" >}}
|
||||||
|
|
||||||
|
The default `same_site` value for all `cookies` configurations.
|
||||||
|
|
||||||
### inactivity
|
### inactivity
|
||||||
|
|
||||||
|
@ -108,18 +102,119 @@ The period of time before the cookie expires and the session is destroyed. This
|
||||||
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
||||||
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
||||||
|
|
||||||
The period of time the user can be inactive for until the session is destroyed. Useful if you want long session timers
|
The default `inactivity` value for all [cookies](#cookies) configurations.
|
||||||
but don't want unused devices to be vulnerable.
|
|
||||||
|
|
||||||
### remember_me_duration
|
### expiration
|
||||||
|
|
||||||
|
{{< confkey type="duration" default="1h" required="no" >}}
|
||||||
|
|
||||||
|
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
||||||
|
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
||||||
|
|
||||||
|
The default `expiration` value for all [cookies](#cookies) configurations.
|
||||||
|
|
||||||
|
### remember_me
|
||||||
|
|
||||||
{{< confkey type="duration" default="1M" required="no" >}}
|
{{< confkey type="duration" default="1M" required="no" >}}
|
||||||
|
|
||||||
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
||||||
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
||||||
|
|
||||||
|
The default `remember_me` value for all [cookies](#cookies) configurations.
|
||||||
|
|
||||||
|
### cookies
|
||||||
|
|
||||||
|
The list of specific cookie domains that Authelia is configured to handle. Domains not properly configured will
|
||||||
|
automatically be denied by Authelia. The list allows administrators to define multiple session cookie domain
|
||||||
|
configurations with individual settings.
|
||||||
|
|
||||||
|
#### name
|
||||||
|
|
||||||
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
||||||
|
*__Default Value:__ This option takes its default value from the [name](#name) setting above.*
|
||||||
|
|
||||||
|
The name of the session cookie. By default this is set to the `name` value in the main session configuration section.
|
||||||
|
|
||||||
|
#### domain
|
||||||
|
|
||||||
|
{{< confkey type="string" required="yes" >}}
|
||||||
|
|
||||||
|
The domain the cookie is assigned to protect. This must be the same as the domain Authelia is served on or the root
|
||||||
|
of the domain, and consequently if the [authelia_url](#authelia_url) is configured must be able to read and write cookies
|
||||||
|
for the domain. For example if listening on `auth.example.com` the cookie should be either `auth.example.com` or
|
||||||
|
`example.com`.
|
||||||
|
|
||||||
|
Please note most good DynamicDNS solutions fall into a specially protected group of domains and browsers do not allow
|
||||||
|
you to write cookies for the root domain. i.e. if you have been assigned `john.duckdns.org` you can't use `duckdns.org`
|
||||||
|
for the domain value as browsers will not allow `john.duckdns.org` to read or write cookies for `duckdns.org`.
|
||||||
|
|
||||||
|
Consequently, if you have `john.duckdns.org` and `mary.duckdns.org` you cannot share cookies between these domains.
|
||||||
|
|
||||||
|
#### authelia_url
|
||||||
|
|
||||||
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
||||||
|
*__Note:__ The AuthRequest implementation does not support redirection control on the authorization server. This means
|
||||||
|
that the `authelia_url` option is ineffectual for both NGINX and HAProxy, or any other proxy which uses the AuthRequest
|
||||||
|
implementation.*
|
||||||
|
|
||||||
|
This is a completely optional URL which is the root URL of your Authelia installation for this cookie domain which can
|
||||||
|
be used to generate the appropriate redirection for proxies which support this.
|
||||||
|
|
||||||
|
If this option is absent you must use the appropriate query parameter or header for your relevant proxy.
|
||||||
|
|
||||||
|
#### same_site
|
||||||
|
|
||||||
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
||||||
|
*__Default Value:__ This option takes its default value from the [same_site](#samesite) setting above.*
|
||||||
|
|
||||||
|
Sets the cookies SameSite value. Prior to offering the configuration choice this defaulted to None. The new default is
|
||||||
|
Lax. This option is defined in lower-case. So for example if you want to set it to `Strict`, the value in configuration
|
||||||
|
needs to be `strict`.
|
||||||
|
|
||||||
|
You can read about the SameSite cookie in detail on the
|
||||||
|
[MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite). In short setting SameSite to Lax
|
||||||
|
is generally the most desirable option for Authelia. None is not recommended unless you absolutely know what you're
|
||||||
|
doing and trust all the protected apps. Strict is not going to work in many use cases and we have not tested it in this
|
||||||
|
state but it's available as an option anyway.
|
||||||
|
|
||||||
|
#### inactivity
|
||||||
|
|
||||||
|
{{< confkey type="duration" required="no" >}}
|
||||||
|
|
||||||
|
*__Default Value:__ This option takes its default value from the [inactivity](#inactivity) setting above.*
|
||||||
|
|
||||||
|
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
||||||
|
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
||||||
|
|
||||||
|
The period of time the user can be inactive for until the session is destroyed. Useful if you want long session timers
|
||||||
|
but don't want unused devices to be vulnerable.
|
||||||
|
|
||||||
|
#### expiration
|
||||||
|
|
||||||
|
{{< confkey type="duration" required="no" >}}
|
||||||
|
|
||||||
|
*__Default Value:__ This option takes its default value from the [expiration](#expiration) setting above.*
|
||||||
|
|
||||||
|
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
||||||
|
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
||||||
|
|
||||||
|
The period of time before the cookie expires and the session is destroyed. This is overriden by
|
||||||
|
[remember_me](#rememberme) when the remember me box is checked.
|
||||||
|
|
||||||
|
#### remember_me
|
||||||
|
|
||||||
|
{{< confkey type="duration" required="no" >}}
|
||||||
|
|
||||||
|
*__Default Value:__ This option takes its default value from the [remember_me](#rememberme) setting above.*
|
||||||
|
|
||||||
|
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
||||||
|
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
||||||
|
|
||||||
The period of time before the cookie expires and the session is destroyed when the remember me box is checked. Setting
|
The period of time before the cookie expires and the session is destroyed when the remember me box is checked. Setting
|
||||||
this to `-1` disables this feature entirely.
|
this to `-1` disables this feature entirely for this session cookie domain.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,8 @@ for, and the structure it must have.
|
||||||
│ └─⫸ Commit Scope: api|autheliabot|authentication|authorization|buildkite|bundler|cmd|
|
│ └─⫸ Commit Scope: api|autheliabot|authentication|authorization|buildkite|bundler|cmd|
|
||||||
│ codecov|commands|configuration|deps|docker|duo|go|golangci-lint|
|
│ codecov|commands|configuration|deps|docker|duo|go|golangci-lint|
|
||||||
│ handlers|logging|metrics|middlewares|mocks|model|notification|npm|ntp|
|
│ handlers|logging|metrics|middlewares|mocks|model|notification|npm|ntp|
|
||||||
│ oidc|regulation|renovate|reviewdog|server|session|storage|suites|
|
│ oidc|random|regulation|renovate|reviewdog|server|session|storage|
|
||||||
│ templates|totp|utils|web
|
│ suites|templates|totp|utils|web
|
||||||
│
|
│
|
||||||
└─⫸ Commit Type: build|ci|docs|feat|fix|i18n|perf|refactor|release|revert|test
|
└─⫸ Commit Type: build|ci|docs|feat|fix|i18n|perf|refactor|release|revert|test
|
||||||
```
|
```
|
||||||
|
@ -93,6 +93,7 @@ commit messages).
|
||||||
* notification
|
* notification
|
||||||
* ntp
|
* ntp
|
||||||
* oidc
|
* oidc
|
||||||
|
* random
|
||||||
* regulation
|
* regulation
|
||||||
* server
|
* server
|
||||||
* session
|
* session
|
||||||
|
|
|
@ -43,7 +43,7 @@ The following steps will allow you to run the website on the localhost and view
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/authelia/authelia.git
|
git clone https://github.com/authelia/authelia.git
|
||||||
cd authelia/docs
|
cd authelia/docs
|
||||||
npm run install
|
npm install
|
||||||
npm run start
|
npm run start
|
||||||
```
|
```
|
||||||
2. Visit [http://localhost:1313/](http://localhost:1313/) in your browser.
|
2. Visit [http://localhost:1313/](http://localhost:1313/) in your browser.
|
||||||
|
|
|
@ -256,7 +256,7 @@ database. The value of this option should be long and as random as possible. See
|
||||||
[documentation](../../configuration/session/introduction.md#secret) for this option.
|
[documentation](../../configuration/session/introduction.md#secret) for this option.
|
||||||
|
|
||||||
The validity period of session is highly configurable. For example in a highly security conscious domain you could
|
The validity period of session is highly configurable. For example in a highly security conscious domain you could
|
||||||
set the session [remember_me_duration](../../configuration/session/introduction.md#remembermeduration) to 0 to disable this
|
set the session [remember_me](../../configuration/session/introduction.md#rememberme) to 0 to disable this
|
||||||
feature, and set the [expiration](../../configuration/session/introduction.md#expiration) to 2 hours and the
|
feature, and set the [expiration](../../configuration/session/introduction.md#expiration) to 2 hours and the
|
||||||
[inactivity](../../configuration/session/introduction.md#inactivity) of 10 minutes. Configuring the session security in this
|
[inactivity](../../configuration/session/introduction.md#inactivity) of 10 minutes. Configuring the session security in this
|
||||||
manner would mean if the cookie age was more than 2 hours or if the user was inactive for more than 10 minutes the
|
manner would mean if the cookie age was more than 2 hours or if the user was inactive for more than 10 minutes the
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -39,7 +39,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "7.20.7",
|
"@babel/cli": "7.20.7",
|
||||||
"@babel/core": "7.20.7",
|
"@babel/core": "7.20.12",
|
||||||
"@babel/preset-env": "7.20.2",
|
"@babel/preset-env": "7.20.2",
|
||||||
"@fullhuman/postcss-purgecss": "5.0.0",
|
"@fullhuman/postcss-purgecss": "5.0.0",
|
||||||
"@hyas/images": "0.3.2",
|
"@hyas/images": "0.3.2",
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
"bootstrap": "5.2.3",
|
"bootstrap": "5.2.3",
|
||||||
"bootstrap-icons": "1.10.3",
|
"bootstrap-icons": "1.10.3",
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.11",
|
||||||
"eslint": "8.31.0",
|
"eslint": "8.32.0",
|
||||||
"exec-bin": "1.0.0",
|
"exec-bin": "1.0.0",
|
||||||
"flexsearch": "0.7.31",
|
"flexsearch": "0.7.31",
|
||||||
"highlight.js": "11.7.0",
|
"highlight.js": "11.7.0",
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
"markdownlint-cli2": "0.6.0",
|
"markdownlint-cli2": "0.6.0",
|
||||||
"netlify-plugin-submit-sitemap": "0.4.0",
|
"netlify-plugin-submit-sitemap": "0.4.0",
|
||||||
"node-fetch": "3.3.0",
|
"node-fetch": "3.3.0",
|
||||||
"postcss": "8.4.20",
|
"postcss": "8.4.21",
|
||||||
"postcss-cli": "10.1.0",
|
"postcss-cli": "10.1.0",
|
||||||
"purgecss-whitelister": "2.4.0",
|
"purgecss-whitelister": "2.4.0",
|
||||||
"shx": "0.3.4",
|
"shx": "0.3.4",
|
||||||
|
@ -68,6 +68,6 @@
|
||||||
"stylelint-config-standard-scss": "6.1.0"
|
"stylelint-config-standard-scss": "6.1.0"
|
||||||
},
|
},
|
||||||
"otherDependencies": {
|
"otherDependencies": {
|
||||||
"hugo": "0.109.0"
|
"hugo": "0.110.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,6 +3,7 @@
|
||||||
# Authelia configuration #
|
# Authelia configuration #
|
||||||
###############################################################
|
###############################################################
|
||||||
|
|
||||||
|
# This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE
|
||||||
jwt_secret: a_very_important_secret
|
jwt_secret: a_very_important_secret
|
||||||
default_redirection_url: https://public.example.com
|
default_redirection_url: https://public.example.com
|
||||||
|
|
||||||
|
@ -12,7 +13,6 @@ server:
|
||||||
|
|
||||||
log:
|
log:
|
||||||
level: debug
|
level: debug
|
||||||
# This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE
|
|
||||||
|
|
||||||
totp:
|
totp:
|
||||||
issuer: authelia.com
|
issuer: authelia.com
|
||||||
|
@ -39,12 +39,14 @@ access_control:
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
|
|
||||||
session:
|
session:
|
||||||
name: authelia_session
|
|
||||||
# This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE
|
# This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
|
|
||||||
|
cookies:
|
||||||
|
- name: authelia_session
|
||||||
|
domain: example.com # Should match whatever your root protected domain is
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
domain: example.com # Should match whatever your root protected domain is
|
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
host: redis
|
host: redis
|
||||||
|
|
|
@ -31,11 +31,13 @@ access_control:
|
||||||
policy: two_factor
|
policy: two_factor
|
||||||
|
|
||||||
session:
|
session:
|
||||||
name: authelia_session
|
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
|
|
||||||
|
cookies:
|
||||||
|
- name: authelia_session
|
||||||
|
domain: example.com # Should match whatever your root protected domain is
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
domain: example.com # Should match whatever your root protected domain is
|
|
||||||
|
|
||||||
regulation:
|
regulation:
|
||||||
max_retries: 3
|
max_retries: 3
|
||||||
|
|
27
go.mod
27
go.mod
|
@ -7,28 +7,28 @@ require (
|
||||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||||
github.com/deckarep/golang-set/v2 v2.1.0
|
github.com/deckarep/golang-set/v2 v2.1.0
|
||||||
github.com/duosecurity/duo_api_golang v0.0.0-20221117185402-091daa09e19d
|
github.com/duosecurity/duo_api_golang v0.0.0-20221117185402-091daa09e19d
|
||||||
github.com/fasthttp/router v1.4.14
|
github.com/fasthttp/router v1.4.15
|
||||||
github.com/fasthttp/session/v2 v2.4.13
|
github.com/fasthttp/session/v2 v2.4.16
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.4
|
github.com/go-asn1-ber/asn1-ber v1.5.4
|
||||||
github.com/go-crypt/crypt v0.2.3
|
github.com/go-crypt/crypt v0.2.3
|
||||||
github.com/go-ldap/ldap/v3 v3.4.4
|
github.com/go-ldap/ldap/v3 v3.4.4
|
||||||
github.com/go-rod/rod v0.112.2
|
github.com/go-rod/rod v0.112.3
|
||||||
github.com/go-sql-driver/mysql v1.7.0
|
github.com/go-sql-driver/mysql v1.7.0
|
||||||
github.com/go-webauthn/webauthn v0.5.0
|
github.com/go-webauthn/webauthn v0.6.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3
|
github.com/golang-jwt/jwt/v4 v4.4.3
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.2
|
github.com/hashicorp/go-retryablehttp v0.7.2
|
||||||
github.com/jackc/pgx/v5 v5.2.0
|
github.com/jackc/pgx/v5 v5.2.0
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/jmoiron/sqlx v1.3.5
|
||||||
github.com/knadh/koanf v1.4.5
|
github.com/knadh/koanf v1.5.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.16
|
github.com/mattn/go-sqlite3 v1.14.16
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||||
github.com/ory/fosite v0.44.0
|
github.com/ory/fosite v0.44.0
|
||||||
github.com/ory/herodot v0.9.13
|
github.com/ory/herodot v0.9.13
|
||||||
github.com/ory/x v0.0.528
|
github.com/ory/x v0.0.533
|
||||||
github.com/otiai10/copy v1.9.0
|
github.com/otiai10/copy v1.9.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
|
@ -38,8 +38,8 @@ require (
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/trustelem/zxcvbn v1.0.1
|
github.com/trustelem/zxcvbn v1.0.1
|
||||||
github.com/valyala/fasthttp v1.43.0
|
github.com/valyala/fasthttp v1.44.0
|
||||||
github.com/wneessen/go-mail v0.3.7
|
github.com/wneessen/go-mail v0.3.8
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/sync v0.1.0
|
||||||
golang.org/x/term v0.4.0
|
golang.org/x/term v0.4.0
|
||||||
golang.org/x/text v0.6.0
|
golang.org/x/text v0.6.0
|
||||||
|
@ -88,31 +88,30 @@ require (
|
||||||
github.com/ory/viper v1.7.5 // indirect
|
github.com/ory/viper v1.7.5 // indirect
|
||||||
github.com/pborman/uuid v1.2.1 // indirect
|
github.com/pborman/uuid v1.2.1 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/philhofer/fwd v1.1.1 // indirect
|
github.com/philhofer/fwd v1.1.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d // indirect
|
|
||||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect
|
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect
|
||||||
github.com/spf13/afero v1.9.2 // indirect
|
github.com/spf13/afero v1.9.2 // indirect
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/subosito/gotenv v1.4.1 // indirect
|
github.com/subosito/gotenv v1.4.1 // indirect
|
||||||
github.com/test-go/testify v1.1.4 // indirect
|
github.com/test-go/testify v1.1.4 // indirect
|
||||||
github.com/tinylib/msgp v1.1.6 // indirect
|
github.com/tinylib/msgp v1.1.8 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/ysmood/goob v0.4.0 // indirect
|
github.com/ysmood/goob v0.4.0 // indirect
|
||||||
github.com/ysmood/gson v0.7.3 // indirect
|
github.com/ysmood/gson v0.7.3 // indirect
|
||||||
github.com/ysmood/leakless v0.8.0 // indirect
|
github.com/ysmood/leakless v0.8.0 // indirect
|
||||||
golang.org/x/crypto v0.1.0 // indirect
|
golang.org/x/crypto v0.4.0 // indirect
|
||||||
golang.org/x/mod v0.6.0 // indirect
|
golang.org/x/mod v0.7.0 // indirect
|
||||||
golang.org/x/net v0.5.0 // indirect
|
golang.org/x/net v0.5.0 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.4.0 // indirect
|
||||||
golang.org/x/tools v0.2.0 // indirect
|
golang.org/x/tools v0.4.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect
|
google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect
|
||||||
google.golang.org/grpc v1.50.1 // indirect
|
google.golang.org/grpc v1.50.1 // indirect
|
||||||
|
|
75
go.sum
75
go.sum
|
@ -75,7 +75,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
@ -144,10 +144,10 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||||
github.com/fasthttp/router v1.4.14 h1:+W65VCKgyI4BZszhDiCRfONoFieePZIoQ7D8vGhiuzM=
|
github.com/fasthttp/router v1.4.15 h1:ERaILezYX6ks1I+Z2v5qY4vqiQKnujauo9nV6M+HIOg=
|
||||||
github.com/fasthttp/router v1.4.14/go.mod h1:+svLaOvqF9Lc0yjX9aHAD4NUMf+mggLPOT4UMdS6fjM=
|
github.com/fasthttp/router v1.4.15/go.mod h1:NFNlTCilbRVkeLc+E5JDkcxUdkpiJGKDL8Zy7Ey2JTI=
|
||||||
github.com/fasthttp/session/v2 v2.4.13 h1:I/j3w8UrXX1haXE+iraAbQuGihNVeTq6b8sp6L3ZJ6Q=
|
github.com/fasthttp/session/v2 v2.4.16 h1:JRvuEqr/+/cNMBkhGZN118FurLh6paUGscwJr26TxAQ=
|
||||||
github.com/fasthttp/session/v2 v2.4.13/go.mod h1:bAE6Bjl6ofQbkOpqcSuOVt/1R1LnbNLnFMHjGQcYP5M=
|
github.com/fasthttp/session/v2 v2.4.16/go.mod h1:nv8SD6pAx3n3KjJsEt4k1p0vstqclbNcrCwjc1OjuCI=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
|
@ -182,8 +182,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
||||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
github.com/go-rod/rod v0.112.2 h1:dwauKYC/H2em8/BcGk3gC0LTzZHf5MIDKf2DVM4z9gU=
|
github.com/go-rod/rod v0.112.3 h1:xbSaA9trZ8v/+eJRGOM6exK1RCsLPwwnzA78vpES0gk=
|
||||||
github.com/go-rod/rod v0.112.2/go.mod h1:ElViL9ABbcshNQw93+11FrYRH92RRhMKleuILo6+5V0=
|
github.com/go-rod/rod v0.112.3/go.mod h1:ElViL9ABbcshNQw93+11FrYRH92RRhMKleuILo6+5V0=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
@ -192,8 +192,8 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78
|
||||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/go-webauthn/revoke v0.1.6 h1:3tv+itza9WpX5tryRQx4GwxCCBrCIiJ8GIkOhxiAmmU=
|
github.com/go-webauthn/revoke v0.1.6 h1:3tv+itza9WpX5tryRQx4GwxCCBrCIiJ8GIkOhxiAmmU=
|
||||||
github.com/go-webauthn/revoke v0.1.6/go.mod h1:TB4wuW4tPlwgF3znujA96F70/YSQXHPPWl7vgY09Iy8=
|
github.com/go-webauthn/revoke v0.1.6/go.mod h1:TB4wuW4tPlwgF3znujA96F70/YSQXHPPWl7vgY09Iy8=
|
||||||
github.com/go-webauthn/webauthn v0.5.0 h1:Tbmp37AGIhYbQmcy2hEffo3U3cgPClqvxJ7cLUnF7Rc=
|
github.com/go-webauthn/webauthn v0.6.0 h1:uLInMApSvBfP+vEFasNE0rnVPG++fjp7lmAIvNhe+UU=
|
||||||
github.com/go-webauthn/webauthn v0.5.0/go.mod h1:0CBq/jNfPS9l033j4AxMk8K8MluiMsde9uGNSPFLEVE=
|
github.com/go-webauthn/webauthn v0.6.0/go.mod h1:7edMRZXwuM6JIVjN68G24Bzt+bPCvTmjiL0j+cAmXtY=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
|
@ -363,11 +363,10 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
|
||||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
github.com/knadh/koanf v1.4.5 h1:yKWFswTrqFc0u7jBAoERUz30+N1b1yPXU01gAPr8IrY=
|
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
||||||
github.com/knadh/koanf v1.4.5/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
|
@ -396,7 +395,6 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mattn/goveralls v0.0.11 h1:eJXea6R6IFlL1QMKNMzDvvHv/hwGrnvyig4N+0+XiMM=
|
github.com/mattn/goveralls v0.0.11 h1:eJXea6R6IFlL1QMKNMzDvvHv/hwGrnvyig4N+0+XiMM=
|
||||||
|
@ -461,8 +459,8 @@ github.com/ory/herodot v0.9.13 h1:cN/Z4eOkErl/9W7hDIDLb79IO/bfsH+8yscBjRpB4IU=
|
||||||
github.com/ory/herodot v0.9.13/go.mod h1:IWDs9kSvFQqw/cQ8zi5ksyYvITiUU4dI7glUrhZcJYo=
|
github.com/ory/herodot v0.9.13/go.mod h1:IWDs9kSvFQqw/cQ8zi5ksyYvITiUU4dI7glUrhZcJYo=
|
||||||
github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE=
|
github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE=
|
||||||
github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=
|
github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=
|
||||||
github.com/ory/x v0.0.528 h1:26fXxJ5Zl5XDFjiCt5jdWQOI89Q2XogB1EnUeqx7P+M=
|
github.com/ory/x v0.0.533 h1:F7kJ1C5jQv4PCmbOppK8H3V0mV2yQeOy97zCgDIHb4U=
|
||||||
github.com/ory/x v0.0.528/go.mod h1:XBqhPZRppPHTxtsE0l0oI/B2Onf1QJtMRGPh3CpEpA0=
|
github.com/ory/x v0.0.533/go.mod h1:6mu7krkQuaDXxxeVb/SplbrXSlItcklT51nKBihmUl4=
|
||||||
github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4=
|
github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4=
|
||||||
github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI=
|
github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI=
|
||||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||||
|
@ -482,8 +480,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||||
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@ -540,9 +538,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||||
github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d h1:ICMDEgNgR5xFW6ZDeMKTtmh07YiLr7GkDw897I2DwKg=
|
|
||||||
github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d/go.mod h1:jrsy/bTK2n5uybo7bAvtLGzmuzAbxp+nKS8bzgrZURE=
|
|
||||||
github.com/savsgio/gotils v0.0.0-20220401102855-e56b59f40436/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
|
||||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo=
|
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo=
|
||||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
|
@ -595,8 +590,8 @@ github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs
|
||||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||||
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
|
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
|
||||||
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
|
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
|
||||||
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
|
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/trustelem/zxcvbn v1.0.1 h1:mp4JFtzdDYGj9WYSD3KQSkwwUumWNFzXaAjckaTYpsc=
|
github.com/trustelem/zxcvbn v1.0.1 h1:mp4JFtzdDYGj9WYSD3KQSkwwUumWNFzXaAjckaTYpsc=
|
||||||
github.com/trustelem/zxcvbn v1.0.1/go.mod h1:zonUyKeh7sw6psPf/e3DtRqkRyZvAbOfjNz/aO7YQ5s=
|
github.com/trustelem/zxcvbn v1.0.1/go.mod h1:zonUyKeh7sw6psPf/e3DtRqkRyZvAbOfjNz/aO7YQ5s=
|
||||||
|
@ -604,13 +599,11 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
|
github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q=
|
||||||
github.com/valyala/fasthttp v1.42.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||||
github.com/valyala/fasthttp v1.43.0 h1:Gy4sb32C98fbzVWZlTM1oTMdLWGyvxR03VhM6cBIU4g=
|
|
||||||
github.com/valyala/fasthttp v1.43.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
|
||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
github.com/wneessen/go-mail v0.3.7 h1:loEAGLvsDZLSiE6c+keBfg0gpias/R3ocFU8Eoh3Pq4=
|
github.com/wneessen/go-mail v0.3.8 h1:ja5D/o/RVwrtRIYFlrO7GmtcjDNeMakGQuwQRZYv0JM=
|
||||||
github.com/wneessen/go-mail v0.3.7/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
github.com/wneessen/go-mail v0.3.8/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
@ -631,6 +624,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||||
|
@ -658,11 +652,12 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -698,8 +693,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||||
|
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -746,7 +742,9 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
@ -772,6 +770,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -843,16 +842,19 @@ golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -865,6 +867,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -916,7 +919,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
@ -928,8 +930,9 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
|
||||||
|
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -172,7 +172,7 @@ func (ctx *CmdCtx) LoadProviders() (warns, errs []error) {
|
||||||
ctx.providers.Notifier = notification.NewFileNotifier(*ctx.config.Notifier.FileSystem)
|
ctx.providers.Notifier = notification.NewFileNotifier(*ctx.config.Notifier.FileSystem)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.providers.OpenIDConnect, err = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, ctx.providers.StorageProvider); err != nil {
|
if ctx.providers.OpenIDConnect, err = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, ctx.providers.StorageProvider, ctx.providers.Templates); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -662,38 +662,76 @@ access_control:
|
||||||
## The session cookies identify the user once logged in.
|
## The session cookies identify the user once logged in.
|
||||||
## The available providers are: `memory`, `redis`. Memory is the provider unless redis is defined.
|
## The available providers are: `memory`, `redis`. Memory is the provider unless redis is defined.
|
||||||
session:
|
session:
|
||||||
|
## The secret to encrypt the session data. This is only used with Redis / Redis Sentinel.
|
||||||
|
## Secret can also be set using a secret: https://www.authelia.com/c/secrets
|
||||||
|
secret: 'insecure_session_secret'
|
||||||
|
|
||||||
|
## Cookies configures the list of allowed cookie domains for sessions to be created on.
|
||||||
|
## Undefined values will default to the values below.
|
||||||
|
# cookies:
|
||||||
|
# -
|
||||||
## The name of the session cookie.
|
## The name of the session cookie.
|
||||||
name: authelia_session
|
# name: 'authelia_session'
|
||||||
|
|
||||||
## The domain to protect.
|
## The domain to protect.
|
||||||
## Note: the authenticator must also be in that domain.
|
## Note: the Authelia portal must also be in that domain.
|
||||||
## If empty, the cookie is restricted to the subdomain of the issuer.
|
# domain: 'example.com'
|
||||||
domain: example.com
|
|
||||||
|
## Optional. The fully qualified URI of the portal to redirect users to on proxies that support redirections.
|
||||||
|
## Rules:
|
||||||
|
## - MUST use the secure scheme 'https://'
|
||||||
|
## - The above domain MUST either:
|
||||||
|
## - Match the host portion of this URI.
|
||||||
|
## - Match the suffix of the host portion when prefixed with '.'.
|
||||||
|
# authelia_url: 'https://auth.example.com'
|
||||||
|
|
||||||
## Sets the Cookie SameSite value. Possible options are none, lax, or strict.
|
## Sets the Cookie SameSite value. Possible options are none, lax, or strict.
|
||||||
## Please read https://www.authelia.com/c/session#same_site
|
## Please read https://www.authelia.com/c/session#same_site
|
||||||
same_site: lax
|
# same_site: 'lax'
|
||||||
|
|
||||||
## The secret to encrypt the session data. This is only used with Redis / Redis Sentinel.
|
## The value for inactivity, expiration, and remember_me are in seconds or the duration notation format.
|
||||||
## Secret can also be set using a secret: https://www.authelia.com/c/secrets
|
|
||||||
secret: insecure_session_secret
|
|
||||||
|
|
||||||
## The value for expiration, inactivity, and remember_me_duration are in seconds or the duration notation format.
|
|
||||||
## See: https://www.authelia.com/c/common#duration-notation-format
|
## See: https://www.authelia.com/c/common#duration-notation-format
|
||||||
## All three of these values affect the cookie/session validity period. Longer periods are considered less secure
|
## All three of these values affect the cookie/session validity period. Longer periods are considered less secure
|
||||||
## because a stolen cookie will last longer giving attackers more time to spy or attack.
|
## because a stolen cookie will last longer giving attackers more time to spy or attack.
|
||||||
|
|
||||||
## The time before the cookie expires and the session is destroyed if remember me IS NOT selected.
|
|
||||||
expiration: 1h
|
|
||||||
|
|
||||||
## The inactivity time before the session is reset. If expiration is set to 1h, and this is set to 5m, if the user
|
## The inactivity time before the session is reset. If expiration is set to 1h, and this is set to 5m, if the user
|
||||||
## does not select the remember me option their session will get destroyed after 1h, or after 5m since the last time
|
## does not select the remember me option their session will get destroyed after 1h, or after 5m since the last
|
||||||
## Authelia detected user activity.
|
## time Authelia detected user activity.
|
||||||
inactivity: 5m
|
# inactivity: '5m'
|
||||||
|
|
||||||
## The time before the cookie expires and the session is destroyed if remember me IS selected.
|
## The time before the session cookie expires and the session is destroyed if remember me IS NOT selected by the
|
||||||
## Value of -1 disables remember me.
|
## user.
|
||||||
remember_me_duration: 1M
|
# expiration: '1h'
|
||||||
|
|
||||||
|
## The time before the cookie expires and the session is destroyed if remember me IS selected by the user. Setting
|
||||||
|
## this value to -1 disables remember me for this session cookie domain.
|
||||||
|
# remember_me: '1M'
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'name' value. The name of the session cookie.
|
||||||
|
name: 'authelia_session'
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'same_site' value. Sets the Cookie SameSite value. Possible options are none, lax,
|
||||||
|
## or strict. Please read https://www.authelia.com/c/session#same_site
|
||||||
|
same_site: 'lax'
|
||||||
|
|
||||||
|
## The value for inactivity, expiration, and remember_me are in seconds or the duration notation format.
|
||||||
|
## See: https://www.authelia.com/c/common#duration-notation-format
|
||||||
|
## All three of these values affect the cookie/session validity period. Longer periods are considered less secure
|
||||||
|
## because a stolen cookie will last longer giving attackers more time to spy or attack.
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'inactivity' value. The inactivity time before the session is reset. If expiration is
|
||||||
|
## set to 1h, and this is set to 5m, if the user does not select the remember me option their session will get
|
||||||
|
## destroyed after 1h, or after 5m since the last time Authelia detected user activity.
|
||||||
|
inactivity: '5m'
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'expiration' value. The time before the session cookie expires and the session is
|
||||||
|
## destroyed if remember me IS NOT selected by the user.
|
||||||
|
expiration: '1h'
|
||||||
|
|
||||||
|
## Cookie Session Domain default 'remember_me' value. The time before the cookie expires and the session is destroyed
|
||||||
|
## if remember me IS selected by the user. Setting this value to -1 disables remember me for all session cookie
|
||||||
|
## domains which do not have a specific 'remember_me' value.
|
||||||
|
remember_me: '1M'
|
||||||
|
|
||||||
##
|
##
|
||||||
## Redis Provider
|
## Redis Provider
|
||||||
|
|
|
@ -134,4 +134,11 @@ var deprecations = map[string]Deprecation{
|
||||||
AutoMap: true,
|
AutoMap: true,
|
||||||
MapFunc: nil,
|
MapFunc: nil,
|
||||||
},
|
},
|
||||||
|
"session.remember_me_duration": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 38},
|
||||||
|
Key: "session.remember_me_duration",
|
||||||
|
NewKey: "session.remember_me",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -323,7 +323,7 @@ func TestShouldDecodeSMTPSenderWithName(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, "Admin", config.Notifier.SMTP.Sender.Name)
|
assert.Equal(t, "Admin", config.Notifier.SMTP.Sender.Name)
|
||||||
assert.Equal(t, "admin@example.com", config.Notifier.SMTP.Sender.Address)
|
assert.Equal(t, "admin@example.com", config.Notifier.SMTP.Sender.Address)
|
||||||
assert.Equal(t, schema.RememberMeDisabled, config.Session.RememberMeDuration)
|
assert.Equal(t, schema.RememberMeDisabled, config.Session.RememberMe)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldParseRegex(t *testing.T) {
|
func TestShouldParseRegex(t *testing.T) {
|
||||||
|
|
|
@ -105,13 +105,23 @@ var Keys = []string{
|
||||||
"authentication_backend.ldap.permit_feature_detection_failure",
|
"authentication_backend.ldap.permit_feature_detection_failure",
|
||||||
"authentication_backend.ldap.user",
|
"authentication_backend.ldap.user",
|
||||||
"authentication_backend.ldap.password",
|
"authentication_backend.ldap.password",
|
||||||
|
"session.secret",
|
||||||
"session.name",
|
"session.name",
|
||||||
"session.domain",
|
"session.domain",
|
||||||
"session.same_site",
|
"session.same_site",
|
||||||
"session.secret",
|
|
||||||
"session.expiration",
|
"session.expiration",
|
||||||
"session.inactivity",
|
"session.inactivity",
|
||||||
"session.remember_me_duration",
|
"session.remember_me",
|
||||||
|
"session",
|
||||||
|
"session.cookies",
|
||||||
|
"session.cookies[].name",
|
||||||
|
"session.cookies[].domain",
|
||||||
|
"session.cookies[].same_site",
|
||||||
|
"session.cookies[].expiration",
|
||||||
|
"session.cookies[].inactivity",
|
||||||
|
"session.cookies[].remember_me",
|
||||||
|
"session.cookies[]",
|
||||||
|
"session.cookies[].authelia_url",
|
||||||
"session.redis.host",
|
"session.redis.host",
|
||||||
"session.redis.port",
|
"session.redis.port",
|
||||||
"session.redis.username",
|
"session.redis.username",
|
||||||
|
|
|
@ -2,6 +2,7 @@ package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,24 +37,42 @@ type RedisSessionConfiguration struct {
|
||||||
|
|
||||||
// SessionConfiguration represents the configuration related to user sessions.
|
// SessionConfiguration represents the configuration related to user sessions.
|
||||||
type SessionConfiguration struct {
|
type SessionConfiguration struct {
|
||||||
Name string `koanf:"name"`
|
|
||||||
Domain string `koanf:"domain"`
|
|
||||||
SameSite string `koanf:"same_site"`
|
|
||||||
Secret string `koanf:"secret"`
|
Secret string `koanf:"secret"`
|
||||||
Expiration time.Duration `koanf:"expiration"`
|
|
||||||
Inactivity time.Duration `koanf:"inactivity"`
|
SessionCookieCommonConfiguration `koanf:",squash"`
|
||||||
RememberMeDuration time.Duration `koanf:"remember_me_duration"`
|
|
||||||
|
Cookies []SessionCookieConfiguration `koanf:"cookies"`
|
||||||
|
|
||||||
Redis *RedisSessionConfiguration `koanf:"redis"`
|
Redis *RedisSessionConfiguration `koanf:"redis"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionCookieCommonConfiguration struct {
|
||||||
|
Name string `koanf:"name"`
|
||||||
|
Domain string `koanf:"domain"`
|
||||||
|
SameSite string `koanf:"same_site"`
|
||||||
|
Expiration time.Duration `koanf:"expiration"`
|
||||||
|
Inactivity time.Duration `koanf:"inactivity"`
|
||||||
|
RememberMe time.Duration `koanf:"remember_me"`
|
||||||
|
|
||||||
|
DisableRememberMe bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionCookieConfiguration represents the configuration for a cookie domain.
|
||||||
|
type SessionCookieConfiguration struct {
|
||||||
|
SessionCookieCommonConfiguration `koanf:",squash"`
|
||||||
|
|
||||||
|
AutheliaURL *url.URL `koanf:"authelia_url"`
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultSessionConfiguration is the default session configuration.
|
// DefaultSessionConfiguration is the default session configuration.
|
||||||
var DefaultSessionConfiguration = SessionConfiguration{
|
var DefaultSessionConfiguration = SessionConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: SessionCookieCommonConfiguration{
|
||||||
Name: "authelia_session",
|
Name: "authelia_session",
|
||||||
Expiration: time.Hour,
|
Expiration: time.Hour,
|
||||||
Inactivity: time.Minute * 5,
|
Inactivity: time.Minute * 5,
|
||||||
RememberMeDuration: time.Hour * 24 * 30,
|
RememberMe: time.Hour * 24 * 30,
|
||||||
SameSite: "lax",
|
SameSite: "lax",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultRedisConfiguration is the default redis configuration.
|
// DefaultRedisConfiguration is the default redis configuration.
|
||||||
|
|
|
@ -97,7 +97,7 @@ session:
|
||||||
name: authelia_session
|
name: authelia_session
|
||||||
expiration: 3600000 # 1 hour
|
expiration: 3600000 # 1 hour
|
||||||
inactivity: 300000 # 5 minutes
|
inactivity: 300000 # 5 minutes
|
||||||
remember_me_duration: -1
|
remember_me: -1
|
||||||
domain: example.com
|
domain: example.com
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
|
|
|
@ -25,9 +25,15 @@ func newDefaultConfig() schema.Configuration {
|
||||||
DefaultPolicy: "two_factor",
|
DefaultPolicy: "two_factor",
|
||||||
}
|
}
|
||||||
config.Session = schema.SessionConfiguration{
|
config.Session = schema.SessionConfiguration{
|
||||||
Domain: examplecom,
|
|
||||||
Name: "authelia_session",
|
|
||||||
Secret: "secret",
|
Secret: "secret",
|
||||||
|
Cookies: []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session",
|
||||||
|
Domain: exampleDotCom,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
config.Storage.EncryptionKey = testEncryptionKey
|
config.Storage.EncryptionKey = testEncryptionKey
|
||||||
config.Storage.Local = &schema.LocalStorageConfiguration{
|
config.Storage.Local = &schema.LocalStorageConfiguration{
|
||||||
|
@ -49,6 +55,8 @@ func TestShouldEnsureNotifierConfigIsProvided(t *testing.T) {
|
||||||
ValidateConfiguration(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
|
|
||||||
|
config = newDefaultConfig()
|
||||||
|
|
||||||
config.Notifier.SMTP = nil
|
config.Notifier.SMTP = nil
|
||||||
config.Notifier.FileSystem = nil
|
config.Notifier.FileSystem = nil
|
||||||
|
|
||||||
|
@ -135,6 +143,8 @@ func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
|
||||||
|
|
||||||
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
|
assert.EqualError(t, validator.Warnings()[0], "access control: no rules have been specified so the 'default_policy' of 'two_factor' is going to be applied to all requests")
|
||||||
|
|
||||||
|
config = newDefaultConfig()
|
||||||
|
|
||||||
validator = schema.NewStructValidator()
|
validator = schema.NewStructValidator()
|
||||||
config.CertificatesDirectory = "const.go"
|
config.CertificatesDirectory = "const.go"
|
||||||
|
|
||||||
|
|
|
@ -248,7 +248,7 @@ const (
|
||||||
// Session error constants.
|
// Session error constants.
|
||||||
const (
|
const (
|
||||||
errFmtSessionOptionRequired = "session: option '%s' is required"
|
errFmtSessionOptionRequired = "session: option '%s' is required"
|
||||||
errFmtSessionDomainMustBeRoot = "session: option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '%s'"
|
errFmtSessionLegacyAndWarning = "session: option 'domain' and option 'cookies' can't be specified at the same time"
|
||||||
errFmtSessionSameSite = "session: option 'same_site' must be one of '%s' but is configured as '%s'"
|
errFmtSessionSameSite = "session: option 'same_site' must be one of '%s' but is configured as '%s'"
|
||||||
errFmtSessionSecretRequired = "session: option 'secret' is required when using the '%s' provider"
|
errFmtSessionSecretRequired = "session: option 'secret' is required when using the '%s' provider"
|
||||||
errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but is configured as '%d'"
|
errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but is configured as '%d'"
|
||||||
|
@ -258,6 +258,16 @@ const (
|
||||||
|
|
||||||
errFmtSessionRedisSentinelMissingName = "session: redis: high_availability: option 'sentinel_name' is required"
|
errFmtSessionRedisSentinelMissingName = "session: redis: high_availability: option 'sentinel_name' is required"
|
||||||
errFmtSessionRedisSentinelNodeHostMissing = "session: redis: high_availability: option 'nodes': option 'host' is required for each node but one or more nodes are missing this"
|
errFmtSessionRedisSentinelNodeHostMissing = "session: redis: high_availability: option 'nodes': option 'host' is required for each node but one or more nodes are missing this"
|
||||||
|
|
||||||
|
errFmtSessionDomainMustBeRoot = "session: domain config %s: option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '%s'"
|
||||||
|
errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of '%s' but is configured as '%s'"
|
||||||
|
errFmtSessionDomainRequired = "session: domain config %s: option 'domain' is required"
|
||||||
|
errFmtSessionDomainHasPeriodPrefix = "session: domain config %s: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it"
|
||||||
|
errFmtSessionDomainDuplicate = "session: domain config %s: option 'domain' is a duplicate value for another configured session domain"
|
||||||
|
errFmtSessionDomainDuplicateCookieScope = "session: domain config %s: option 'domain' shares the same cookie domain scope as another configured session domain"
|
||||||
|
errFmtSessionDomainPortalURLInsecure = "session: domain config %s: option 'authelia_url' does not have a secure scheme with a value of '%s'"
|
||||||
|
errFmtSessionDomainPortalURLNotInCookieScope = "session: domain config %s: option 'authelia_url' does not share a cookie scope with domain '%s' with a value of '%s'"
|
||||||
|
errFmtSessionDomainInvalidDomain = "session: domain config %s: option 'domain' is not a valid domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Regulation Error Consts.
|
// Regulation Error Consts.
|
||||||
|
@ -363,7 +373,10 @@ var (
|
||||||
validOIDCClientConsentModes = []string{"auto", oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
|
validOIDCClientConsentModes = []string{"auto", oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
|
||||||
)
|
)
|
||||||
|
|
||||||
var reKeyReplacer = regexp.MustCompile(`\[\d+]`)
|
var (
|
||||||
|
reKeyReplacer = regexp.MustCompile(`\[\d+]`)
|
||||||
|
reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`)
|
||||||
|
)
|
||||||
|
|
||||||
var replacedKeys = map[string]string{
|
var replacedKeys = map[string]string{
|
||||||
"authentication_backend.ldap.skip_verify": "authentication_backend.ldap.tls.skip_verify",
|
"authentication_backend.ldap.skip_verify": "authentication_backend.ldap.tls.skip_verify",
|
||||||
|
|
|
@ -12,5 +12,5 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
examplecom = "example.com"
|
exampleDotCom = "example.com"
|
||||||
)
|
)
|
||||||
|
|
|
@ -257,7 +257,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
|
||||||
RedirectURIs: []string{
|
RedirectURIs: []string{
|
||||||
"https://google.com",
|
"https://google.com",
|
||||||
},
|
},
|
||||||
SectorIdentifier: mustParseURL(examplecom),
|
SectorIdentifier: mustParseURL(exampleDotCom),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -289,12 +289,12 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Errors: []string{
|
Errors: []string{
|
||||||
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "scheme", "https"),
|
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "scheme", "https"),
|
||||||
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "path", "/path"),
|
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "path", "/path"),
|
||||||
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "query", "query=abc"),
|
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "query", "query=abc"),
|
||||||
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "fragment", "fragment"),
|
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "fragment", "fragment"),
|
||||||
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "username", "user"),
|
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "username", "user"),
|
||||||
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "password"),
|
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "password"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,7 +23,7 @@ func (suite *NotifierSuite) SetupTest() {
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Password: "password",
|
Password: "password",
|
||||||
Sender: mail.Address{Name: "Authelia", Address: "authelia@example.com"},
|
Sender: mail.Address{Name: "Authelia", Address: "authelia@example.com"},
|
||||||
Host: examplecom,
|
Host: exampleDotCom,
|
||||||
Port: 25,
|
Port: 25,
|
||||||
}
|
}
|
||||||
suite.config.FileSystem = nil
|
suite.config.FileSystem = nil
|
||||||
|
@ -78,7 +78,7 @@ func (suite *NotifierSuite) TestSMTPShouldSetTLSDefaults() {
|
||||||
suite.Assert().Len(suite.validator.Warnings(), 0)
|
suite.Assert().Len(suite.validator.Warnings(), 0)
|
||||||
suite.Assert().Len(suite.validator.Errors(), 0)
|
suite.Assert().Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Assert().Equal(examplecom, suite.config.SMTP.TLS.ServerName)
|
suite.Assert().Equal(exampleDotCom, suite.config.SMTP.TLS.ServerName)
|
||||||
suite.Assert().Equal(uint16(tls.VersionTLS12), suite.config.SMTP.TLS.MinimumVersion.Value)
|
suite.Assert().Equal(uint16(tls.VersionTLS12), suite.config.SMTP.TLS.MinimumVersion.Value)
|
||||||
suite.Assert().False(suite.config.SMTP.TLS.SkipVerify)
|
suite.Assert().False(suite.config.SMTP.TLS.SkipVerify)
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ func (suite *NotifierSuite) TestSMTPShouldDefaultTLSServerNameToHost() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *NotifierSuite) TestSMTPShouldErrorOnSSL30() {
|
func (suite *NotifierSuite) TestSMTPShouldErrorOnSSL30() {
|
||||||
suite.config.SMTP.Host = examplecom
|
suite.config.SMTP.Host = exampleDotCom
|
||||||
suite.config.SMTP.TLS = &schema.TLSConfig{
|
suite.config.SMTP.TLS = &schema.TLSConfig{
|
||||||
MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck
|
MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ func (suite *NotifierSuite) TestSMTPShouldErrorOnSSL30() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *NotifierSuite) TestSMTPShouldErrorOnTLSMinVerGreaterThanMaxVer() {
|
func (suite *NotifierSuite) TestSMTPShouldErrorOnTLSMinVerGreaterThanMaxVer() {
|
||||||
suite.config.SMTP.Host = examplecom
|
suite.config.SMTP.Host = exampleDotCom
|
||||||
suite.config.SMTP.TLS = &schema.TLSConfig{
|
suite.config.SMTP.TLS = &schema.TLSConfig{
|
||||||
MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
|
MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
|
||||||
MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS10},
|
MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS10},
|
||||||
|
@ -140,7 +140,7 @@ func (suite *NotifierSuite) TestSMTPShouldErrorOnTLSMinVerGreaterThanMaxVer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *NotifierSuite) TestSMTPShouldWarnOnDisabledSTARTTLS() {
|
func (suite *NotifierSuite) TestSMTPShouldWarnOnDisabledSTARTTLS() {
|
||||||
suite.config.SMTP.Host = examplecom
|
suite.config.SMTP.Host = exampleDotCom
|
||||||
suite.config.SMTP.DisableStartTLS = true
|
suite.config.SMTP.DisableStartTLS = true
|
||||||
|
|
||||||
ValidateNotifier(&suite.config, suite.validator)
|
ValidateNotifier(&suite.config, suite.validator)
|
||||||
|
|
|
@ -35,18 +35,11 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru
|
||||||
config.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min.
|
config.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min.
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.RememberMeDuration <= 0 && config.RememberMeDuration != schema.RememberMeDisabled {
|
switch {
|
||||||
config.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month.
|
case config.RememberMe == schema.RememberMeDisabled:
|
||||||
}
|
config.DisableRememberMe = true
|
||||||
|
case config.RememberMe <= 0:
|
||||||
if config.Domain == "" {
|
config.RememberMe = schema.DefaultSessionConfiguration.RememberMe // 1 month.
|
||||||
validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain"))
|
|
||||||
} else if strings.HasPrefix(config.Domain, ".") {
|
|
||||||
validator.PushWarning(fmt.Errorf("session: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(config.Domain, "*.") {
|
|
||||||
validator.Push(fmt.Errorf(errFmtSessionDomainMustBeRoot, config.Domain))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.SameSite == "" {
|
if config.SameSite == "" {
|
||||||
|
@ -54,6 +47,137 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru
|
||||||
} else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) {
|
} else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) {
|
||||||
validator.Push(fmt.Errorf(errFmtSessionSameSite, strings.Join(validSessionSameSiteValues, "', '"), config.SameSite))
|
validator.Push(fmt.Errorf(errFmtSessionSameSite, strings.Join(validSessionSameSiteValues, "', '"), config.SameSite))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cookies := len(config.Cookies)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case cookies == 0 && config.Domain != "":
|
||||||
|
// Add legacy configuration to the domains list.
|
||||||
|
config.Cookies = append(config.Cookies, schema.SessionCookieConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: config.Name,
|
||||||
|
Domain: config.Domain,
|
||||||
|
SameSite: config.SameSite,
|
||||||
|
Expiration: config.Expiration,
|
||||||
|
Inactivity: config.Inactivity,
|
||||||
|
RememberMe: config.RememberMe,
|
||||||
|
DisableRememberMe: config.DisableRememberMe,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
case cookies != 0 && config.Domain != "":
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionLegacyAndWarning))
|
||||||
|
}
|
||||||
|
|
||||||
|
validateSessionCookieDomains(config, validator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSessionCookieDomains(config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
|
if len(config.Cookies) == 0 {
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain"))
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
|
||||||
|
for i, d := range config.Cookies {
|
||||||
|
validateSessionDomainName(i, config, validator)
|
||||||
|
|
||||||
|
validateSessionUniqueCookieDomain(i, config, domains, validator)
|
||||||
|
|
||||||
|
validateSessionCookieName(i, config)
|
||||||
|
|
||||||
|
validateSessionSafeRedirection(i, config, validator)
|
||||||
|
|
||||||
|
validateSessionExpiration(i, config)
|
||||||
|
|
||||||
|
validateSessionRememberMe(i, config)
|
||||||
|
|
||||||
|
validateSessionSameSite(i, config, validator)
|
||||||
|
|
||||||
|
domains = append(domains, d.Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSessionDomainName returns error if the domain name is invalid.
|
||||||
|
func validateSessionDomainName(i int, config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
|
var d = config.Cookies[i]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case d.Domain == "":
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionDomainRequired, sessionDomainDescriptor(i, d)))
|
||||||
|
case strings.HasPrefix(d.Domain, "*."):
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionDomainMustBeRoot, sessionDomainDescriptor(i, d), d.Domain))
|
||||||
|
case strings.HasPrefix(d.Domain, "."):
|
||||||
|
validator.PushWarning(fmt.Errorf(errFmtSessionDomainHasPeriodPrefix, sessionDomainDescriptor(i, d)))
|
||||||
|
case !reDomainCharacters.MatchString(d.Domain):
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionDomainInvalidDomain, sessionDomainDescriptor(i, d)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSessionCookieName(i int, config *schema.SessionConfiguration) {
|
||||||
|
if config.Cookies[i].Name == "" {
|
||||||
|
config.Cookies[i].Name = config.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSessionExpiration(i int, config *schema.SessionConfiguration) {
|
||||||
|
if config.Cookies[i].Expiration <= 0 {
|
||||||
|
config.Cookies[i].Expiration = config.Expiration
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Cookies[i].Inactivity <= 0 {
|
||||||
|
config.Cookies[i].Inactivity = config.Inactivity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSessionUniqueCookieDomain Check the current domains do not share a root domain with previous domains.
|
||||||
|
func validateSessionUniqueCookieDomain(i int, config *schema.SessionConfiguration, domains []string, validator *schema.StructValidator) {
|
||||||
|
var d = config.Cookies[i]
|
||||||
|
if utils.IsStringInSliceF(d.Domain, domains, utils.HasDomainSuffix) {
|
||||||
|
if utils.IsStringInSlice(d.Domain, domains) {
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionDomainDuplicate, sessionDomainDescriptor(i, d)))
|
||||||
|
} else {
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionDomainDuplicateCookieScope, sessionDomainDescriptor(i, d)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSessionSafeRedirection validates that AutheliaURL is safe for redirection.
|
||||||
|
func validateSessionSafeRedirection(index int, config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
|
var d = config.Cookies[index]
|
||||||
|
|
||||||
|
if d.AutheliaURL != nil && d.Domain != "" && !utils.IsURISafeRedirection(d.AutheliaURL, d.Domain) {
|
||||||
|
if utils.IsURISecure(d.AutheliaURL) {
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionDomainPortalURLNotInCookieScope, sessionDomainDescriptor(index, d), d.Domain, d.AutheliaURL))
|
||||||
|
} else {
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionDomainPortalURLInsecure, sessionDomainDescriptor(index, d), d.AutheliaURL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSessionRememberMe(i int, config *schema.SessionConfiguration) {
|
||||||
|
if config.Cookies[i].RememberMe <= 0 && config.Cookies[i].RememberMe != schema.RememberMeDisabled {
|
||||||
|
config.Cookies[i].RememberMe = config.RememberMe
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Cookies[i].RememberMe == schema.RememberMeDisabled {
|
||||||
|
config.Cookies[i].DisableRememberMe = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSessionSameSite(i int, config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
|
if config.Cookies[i].SameSite == "" {
|
||||||
|
if utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) {
|
||||||
|
config.Cookies[i].SameSite = config.SameSite
|
||||||
|
} else {
|
||||||
|
config.Cookies[i].SameSite = schema.DefaultSessionConfiguration.SameSite
|
||||||
|
}
|
||||||
|
} else if !utils.IsStringInSlice(config.Cookies[i].SameSite, validSessionSameSiteValues) {
|
||||||
|
validator.Push(fmt.Errorf(errFmtSessionDomainSameSite, sessionDomainDescriptor(i, config.Cookies[i]), strings.Join(validSessionSameSiteValues, "', '"), config.Cookies[i].SameSite))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionDomainDescriptor(position int, domain schema.SessionCookieConfiguration) string {
|
||||||
|
return fmt.Sprintf("#%d (domain '%s')", position+1, domain.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRedisCommon(config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
func validateRedisCommon(config *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
|
|
|
@ -3,7 +3,9 @@ package validator
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -14,7 +16,8 @@ import (
|
||||||
func newDefaultSessionConfig() schema.SessionConfiguration {
|
func newDefaultSessionConfig() schema.SessionConfiguration {
|
||||||
config := schema.SessionConfiguration{}
|
config := schema.SessionConfiguration{}
|
||||||
config.Secret = testJWTSecret
|
config.Secret = testJWTSecret
|
||||||
config.Domain = examplecom
|
config.Domain = exampleDotCom
|
||||||
|
config.Cookies = []schema.SessionCookieConfiguration{}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@ -30,15 +33,144 @@ func TestShouldSetDefaultSessionValues(t *testing.T) {
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.Name, config.Name)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Name, config.Name)
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.RememberMeDuration, config.RememberMeDuration)
|
assert.Equal(t, schema.DefaultSessionConfiguration.RememberMe, config.RememberMe)
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.SameSite, config.SameSite)
|
assert.Equal(t, schema.DefaultSessionConfiguration.SameSite, config.SameSite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldSetDefaultSessionDomainsValues(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have schema.SessionConfiguration
|
||||||
|
expected schema.SessionConfiguration
|
||||||
|
errs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldSetGoodDefaultValues",
|
||||||
|
schema.SessionConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: exampleDotCom, SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema.SessionConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session", Domain: exampleDotCom, SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
|
||||||
|
},
|
||||||
|
Cookies: []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session", Domain: exampleDotCom, SameSite: "lax", Expiration: time.Hour,
|
||||||
|
Inactivity: time.Minute, RememberMe: time.Hour * 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotSetBadDefaultValues",
|
||||||
|
schema.SessionConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
SameSite: "BAD VALUE", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
|
||||||
|
},
|
||||||
|
Cookies: []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session", Domain: exampleDotCom,
|
||||||
|
Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema.SessionConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session", SameSite: "BAD VALUE", Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
|
||||||
|
},
|
||||||
|
Cookies: []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session", Domain: exampleDotCom, SameSite: schema.DefaultSessionConfiguration.SameSite,
|
||||||
|
Expiration: time.Hour, Inactivity: time.Minute, RememberMe: time.Hour * 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'BAD VALUE'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldSetDefaultValuesForEachConfig",
|
||||||
|
schema.SessionConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "default_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute,
|
||||||
|
RememberMe: schema.RememberMeDisabled,
|
||||||
|
},
|
||||||
|
Cookies: []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: exampleDotCom,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: "example2.com", Name: "authelia_session", SameSite: "strict",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema.SessionConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "default_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute,
|
||||||
|
RememberMe: schema.RememberMeDisabled, DisableRememberMe: true,
|
||||||
|
},
|
||||||
|
Cookies: []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "default_session", Domain: exampleDotCom, SameSite: "lax",
|
||||||
|
Expiration: time.Hour, Inactivity: time.Minute, RememberMe: schema.RememberMeDisabled, DisableRememberMe: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session", Domain: "example2.com", SameSite: "strict",
|
||||||
|
Expiration: time.Hour, Inactivity: time.Minute, RememberMe: schema.RememberMeDisabled, DisableRememberMe: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
validator.Clear()
|
||||||
|
|
||||||
|
have := tc.have
|
||||||
|
|
||||||
|
ValidateSession(&have, validator)
|
||||||
|
|
||||||
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
|
|
||||||
|
errs := validator.Errors()
|
||||||
|
require.Len(t, validator.Errors(), len(tc.errs))
|
||||||
|
|
||||||
|
for i, err := range errs {
|
||||||
|
assert.EqualError(t, err, tc.errs[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expected, have)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldSetDefaultSessionValuesWhenNegative(t *testing.T) {
|
func TestShouldSetDefaultSessionValuesWhenNegative(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultSessionConfig()
|
config := newDefaultSessionConfig()
|
||||||
|
|
||||||
config.Expiration, config.Inactivity, config.RememberMeDuration = -1, -1, -2
|
config.Expiration, config.Inactivity, config.RememberMe = -1, -1, -2
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
@ -46,7 +178,7 @@ func TestShouldSetDefaultSessionValuesWhenNegative(t *testing.T) {
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.RememberMeDuration, config.RememberMeDuration)
|
assert.Equal(t, schema.DefaultSessionConfiguration.RememberMe, config.RememberMe)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldWarnSessionValuesWhenPotentiallyInvalid(t *testing.T) {
|
func TestShouldWarnSessionValuesWhenPotentiallyInvalid(t *testing.T) {
|
||||||
|
@ -60,7 +192,7 @@ func TestShouldWarnSessionValuesWhenPotentiallyInvalid(t *testing.T) {
|
||||||
require.Len(t, validator.Warnings(), 1)
|
require.Len(t, validator.Warnings(), 1)
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
|
|
||||||
assert.EqualError(t, validator.Warnings()[0], "session: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it")
|
assert.EqualError(t, validator.Warnings()[0], "session: domain config #1 (domain '.example.com'): option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
|
func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
|
||||||
|
@ -72,6 +204,8 @@ func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
validator.Clear()
|
validator.Clear()
|
||||||
|
|
||||||
|
config = newDefaultSessionConfig()
|
||||||
|
|
||||||
// Set redis config because password must be set only when redis is used.
|
// Set redis config because password must be set only when redis is used.
|
||||||
config.Redis = &schema.RedisSessionConfiguration{
|
config.Redis = &schema.RedisSessionConfiguration{
|
||||||
Host: "redis.localhost",
|
Host: "redis.localhost",
|
||||||
|
@ -81,8 +215,8 @@ func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
assert.False(t, validator.HasErrors())
|
assert.Len(t, validator.Errors(), 0)
|
||||||
|
|
||||||
assert.Equal(t, 8, config.Redis.MaximumActiveConnections)
|
assert.Equal(t, 8, config.Redis.MaximumActiveConnections)
|
||||||
}
|
}
|
||||||
|
@ -98,7 +232,7 @@ func TestShouldRaiseErrorWithInvalidRedisPortLow(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
require.Len(t, validator.Warnings(), 0)
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
|
|
||||||
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisPortRange, -1))
|
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisPortRange, -1))
|
||||||
|
@ -131,6 +265,9 @@ func TestShouldRaiseErrorWhenRedisIsUsedAndSecretNotSet(t *testing.T) {
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
validator.Clear()
|
validator.Clear()
|
||||||
|
|
||||||
|
config = newDefaultSessionConfig()
|
||||||
|
config.Secret = ""
|
||||||
|
|
||||||
// Set redis config because password must be set only when redis is used.
|
// Set redis config because password must be set only when redis is used.
|
||||||
config.Redis = &schema.RedisSessionConfiguration{
|
config.Redis = &schema.RedisSessionConfiguration{
|
||||||
Host: "redis.localhost",
|
Host: "redis.localhost",
|
||||||
|
@ -153,6 +290,8 @@ func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
validator.Clear()
|
validator.Clear()
|
||||||
|
|
||||||
|
config = newDefaultSessionConfig()
|
||||||
|
|
||||||
// Set redis config because password must be set only when redis is used.
|
// Set redis config because password must be set only when redis is used.
|
||||||
config.Redis = &schema.RedisSessionConfiguration{
|
config.Redis = &schema.RedisSessionConfiguration{
|
||||||
Host: "redis.localhost",
|
Host: "redis.localhost",
|
||||||
|
@ -287,7 +426,24 @@ func TestShouldRaiseErrorsWhenRedisSentinelOptionsIncorrectlyConfigured(t *testi
|
||||||
|
|
||||||
validator.Clear()
|
validator.Clear()
|
||||||
|
|
||||||
config.Redis.Port = -1
|
config = newDefaultSessionConfig()
|
||||||
|
|
||||||
|
config.Secret = ""
|
||||||
|
config.Redis = &schema.RedisSessionConfiguration{
|
||||||
|
Port: -1,
|
||||||
|
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
||||||
|
SentinelName: "sentinel",
|
||||||
|
SentinelPassword: "abc123",
|
||||||
|
Nodes: []schema.RedisNode{
|
||||||
|
{
|
||||||
|
Host: "node1",
|
||||||
|
Port: 26379,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RouteByLatency: true,
|
||||||
|
RouteRandomly: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
@ -434,6 +590,7 @@ func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultSessionConfig()
|
config := newDefaultSessionConfig()
|
||||||
config.Domain = ""
|
config.Domain = ""
|
||||||
|
config.Cookies = []schema.SessionCookieConfiguration{}
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
@ -449,9 +606,141 @@ func TestShouldRaiseErrorWhenDomainIsWildcard(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
|
require.Len(t, validator.Errors(), 1)
|
||||||
|
|
||||||
|
assert.EqualError(t, validator.Errors()[0], "session: domain config #1 (domain '*.example.com'): option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '*.example.com'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldRaiseErrorWhenDomainNameIsInvalid(t *testing.T) {
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
|
config := newDefaultSessionConfig()
|
||||||
|
config.Domain = "example!.com"
|
||||||
|
|
||||||
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
|
require.Len(t, validator.Errors(), 1)
|
||||||
|
|
||||||
|
assert.EqualError(t, validator.Errors()[0], "session: domain config #1 (domain 'example!.com'): option 'domain' is not a valid domain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldRaiseErrorWhenHaveDuplicatedDomainName(t *testing.T) {
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
|
config := newDefaultSessionConfig()
|
||||||
|
config.Domain = ""
|
||||||
|
config.Cookies = append(config.Cookies, schema.SessionCookieConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: exampleDotCom,
|
||||||
|
},
|
||||||
|
AutheliaURL: MustParseURL("https://login.example.com"),
|
||||||
|
})
|
||||||
|
config.Cookies = append(config.Cookies, schema.SessionCookieConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: exampleDotCom,
|
||||||
|
},
|
||||||
|
AutheliaURL: MustParseURL("https://login.example.com"),
|
||||||
|
})
|
||||||
|
|
||||||
|
ValidateSession(&config, validator)
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
assert.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "session: option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '*.example.com'")
|
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionDomainDuplicate, sessionDomainDescriptor(1, schema.SessionCookieConfiguration{SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{Domain: exampleDotCom}})))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldRaiseErrorWhenSubdomainConflicts(t *testing.T) {
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
|
config := newDefaultSessionConfig()
|
||||||
|
config.Domain = ""
|
||||||
|
config.Cookies = append(config.Cookies, schema.SessionCookieConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: exampleDotCom,
|
||||||
|
},
|
||||||
|
AutheliaURL: MustParseURL("https://login.example.com"),
|
||||||
|
})
|
||||||
|
config.Cookies = append(config.Cookies, schema.SessionCookieConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: "internal.example.com",
|
||||||
|
},
|
||||||
|
AutheliaURL: MustParseURL("https://login.internal.example.com"),
|
||||||
|
})
|
||||||
|
|
||||||
|
ValidateSession(&config, validator)
|
||||||
|
assert.False(t, validator.HasWarnings())
|
||||||
|
assert.Len(t, validator.Errors(), 1)
|
||||||
|
assert.EqualError(t, validator.Errors()[0], "session: domain config #2 (domain 'internal.example.com'): option 'domain' shares the same cookie domain scope as another configured session domain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldRaiseErrorWhenDomainIsInvalid(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{"ShouldRaiseErrorOnMissingDomain", "", []string{"session: domain config #1 (domain ''): option 'domain' is required"}},
|
||||||
|
{"ShouldNotRaiseErrorOnValidDomain", exampleDotCom, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
|
config := newDefaultSessionConfig()
|
||||||
|
config.Domain = ""
|
||||||
|
|
||||||
|
config.Cookies = []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: tc.have,
|
||||||
|
},
|
||||||
|
AutheliaURL: MustParseURL("https://auth.example.com")},
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
|
require.Len(t, validator.Errors(), len(tc.expected))
|
||||||
|
|
||||||
|
for i, expected := range tc.expected {
|
||||||
|
assert.EqualError(t, validator.Errors()[i], expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldRaiseErrorWhenPortalURLIsInvalid(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{"ShouldRaiseErrorOnInvalidScope", "https://example2.com/login", []string{"session: domain config #1 (domain 'example.com'): option 'authelia_url' does not share a cookie scope with domain 'example.com' with a value of 'https://example2.com/login'"}},
|
||||||
|
{"ShouldRaiseErrorOnInvalidScheme", "http://example.com/login", []string{"session: domain config #1 (domain 'example.com'): option 'authelia_url' does not have a secure scheme with a value of 'http://example.com/login'"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
|
config := newDefaultSessionConfig()
|
||||||
|
config.Domain = ""
|
||||||
|
config.Cookies = []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session",
|
||||||
|
Domain: exampleDotCom,
|
||||||
|
},
|
||||||
|
AutheliaURL: MustParseURL(tc.have)},
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
|
require.Len(t, validator.Errors(), len(tc.expected))
|
||||||
|
|
||||||
|
for i, expected := range tc.expected {
|
||||||
|
assert.EqualError(t, validator.Errors()[i], expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
|
func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
|
||||||
|
@ -462,22 +751,28 @@ func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 2)
|
||||||
|
|
||||||
assert.EqualError(t, validator.Errors()[0], "session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'")
|
assert.EqualError(t, validator.Errors()[0], "session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'")
|
||||||
|
assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
|
func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultSessionConfig()
|
|
||||||
|
var config schema.SessionConfiguration
|
||||||
|
|
||||||
validOptions := []string{"none", "lax", "strict"}
|
validOptions := []string{"none", "lax", "strict"}
|
||||||
|
|
||||||
for _, opt := range validOptions {
|
for _, opt := range validOptions {
|
||||||
|
validator.Clear()
|
||||||
|
|
||||||
|
config = newDefaultSessionConfig()
|
||||||
config.SameSite = opt
|
config.SameSite = opt
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,7 +782,7 @@ func TestShouldSetDefaultWhenNegativeAndNotOverrideDisabledRememberMe(t *testing
|
||||||
config := newDefaultSessionConfig()
|
config := newDefaultSessionConfig()
|
||||||
config.Inactivity = -1
|
config.Inactivity = -1
|
||||||
config.Expiration = -1
|
config.Expiration = -1
|
||||||
config.RememberMeDuration = schema.RememberMeDisabled
|
config.RememberMe = schema.RememberMeDisabled
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
@ -496,7 +791,8 @@ func TestShouldSetDefaultWhenNegativeAndNotOverrideDisabledRememberMe(t *testing
|
||||||
|
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
||||||
assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
|
||||||
assert.Equal(t, schema.RememberMeDisabled, config.RememberMeDuration)
|
assert.Equal(t, schema.RememberMeDisabled, config.RememberMe)
|
||||||
|
assert.True(t, config.DisableRememberMe)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
|
func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
|
||||||
|
@ -505,7 +801,41 @@ func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
assert.False(t, validator.HasWarnings())
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
assert.False(t, validator.HasErrors())
|
assert.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, config.RememberMeDuration, schema.DefaultSessionConfiguration.RememberMeDuration)
|
|
||||||
|
assert.Equal(t, config.RememberMe, schema.DefaultSessionConfiguration.RememberMe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldNotAllowLegacyAndModernCookiesConfig(t *testing.T) {
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
|
config := newDefaultSessionConfig()
|
||||||
|
|
||||||
|
config.Cookies = append(config.Cookies, schema.SessionCookieConfiguration{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: config.Name,
|
||||||
|
Domain: config.Domain,
|
||||||
|
SameSite: config.SameSite,
|
||||||
|
Expiration: config.Expiration,
|
||||||
|
Inactivity: config.Inactivity,
|
||||||
|
RememberMe: config.RememberMe,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
|
require.Len(t, validator.Errors(), 1)
|
||||||
|
|
||||||
|
assert.EqualError(t, validator.Errors()[0], "session: option 'domain' and option 'cookies' can't be specified at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustParseURL(uri string) *url.URL {
|
||||||
|
u, err := url.ParseRequestURI(uri)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,7 @@ const (
|
||||||
testInactivity = time.Second * 10
|
testInactivity = time.Second * 10
|
||||||
testRedirectionURL = "http://redirection.local"
|
testRedirectionURL = "http://redirection.local"
|
||||||
testUsername = "john"
|
testUsername = "john"
|
||||||
|
exampleDotCom = "example.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Duo constants.
|
// Duo constants.
|
||||||
|
|
|
@ -2,9 +2,9 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckSafeRedirectionPOST handler checking whether the redirection to a given URL provided in body is safe.
|
// CheckSafeRedirectionPOST handler checking whether the redirection to a given URL provided in body is safe.
|
||||||
|
@ -16,24 +16,23 @@ func CheckSafeRedirectionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var reqBody checkURIWithinDomainRequestBody
|
var (
|
||||||
|
bodyJSON checkURIWithinDomainRequestBody
|
||||||
|
targetURI *url.URL
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
err := ctx.ParseBody(&reqBody)
|
if err = ctx.ParseBody(&bodyJSON); err != nil {
|
||||||
if err != nil {
|
|
||||||
ctx.Error(fmt.Errorf("unable to parse request body: %w", err), messageOperationFailed)
|
ctx.Error(fmt.Errorf("unable to parse request body: %w", err), messageOperationFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
safe, err := utils.IsURIStringSafeRedirection(reqBody.URI, ctx.Configuration.Session.Domain)
|
if targetURI, err = url.ParseRequestURI(bodyJSON.URI); err != nil {
|
||||||
if err != nil {
|
ctx.Error(fmt.Errorf("unable to determine if uri %s is safe to redirect to: failed to parse URI '%s': %w", bodyJSON.URI, bodyJSON.URI, err), messageOperationFailed)
|
||||||
ctx.Error(fmt.Errorf("unable to determine if uri %s is safe to redirect to: %w", reqBody.URI, err), messageOperationFailed)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.SetJSONBody(checkURIWithinDomainResponseBody{
|
if err = ctx.SetJSONBody(checkURIWithinDomainResponseBody{OK: ctx.IsSafeRedirectionTargetURI(targetURI)}); err != nil {
|
||||||
OK: safe,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(fmt.Errorf("unable to create response body: %w", err), messageOperationFailed)
|
ctx.Error(fmt.Errorf("unable to create response body: %w", err), messageOperationFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,65 +4,79 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exampleDotComDomain = "example.com"
|
func TestCheckSafeRedirection(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
func TestCheckSafeRedirection_ForbiddenCall(t *testing.T) {
|
name string
|
||||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
userSession session.UserSession
|
||||||
Username: "john",
|
have string
|
||||||
AuthenticationLevel: authentication.NotAuthenticated,
|
expected int
|
||||||
})
|
ok bool
|
||||||
defer mock.Close()
|
}{
|
||||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
{
|
||||||
|
"ShouldReturnUnauthorized",
|
||||||
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
session.UserSession{AuthenticationLevel: authentication.NotAuthenticated},
|
||||||
URI: "http://myapp.example.com",
|
"http://myapp.example.com",
|
||||||
})
|
fasthttp.StatusUnauthorized,
|
||||||
|
false,
|
||||||
CheckSafeRedirectionPOST(mock.Ctx)
|
},
|
||||||
assert.Equal(t, 401, mock.Ctx.Response.StatusCode())
|
{
|
||||||
|
"ShouldReturnTrueOnGoodDomain",
|
||||||
|
session.UserSession{Username: "john", AuthenticationLevel: authentication.OneFactor},
|
||||||
|
"https://myapp.example.com",
|
||||||
|
fasthttp.StatusOK,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldReturnFalseOnGoodDomainWithBadScheme",
|
||||||
|
session.UserSession{Username: "john", AuthenticationLevel: authentication.OneFactor},
|
||||||
|
"http://myapp.example.com",
|
||||||
|
fasthttp.StatusOK,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldReturnFalseOnBadDomainWithGoodScheme",
|
||||||
|
session.UserSession{Username: "john", AuthenticationLevel: authentication.OneFactor},
|
||||||
|
"https://myapp.notgood.com",
|
||||||
|
fasthttp.StatusOK,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldReturnFalseOnBadDomainWithBadScheme",
|
||||||
|
session.UserSession{Username: "john", AuthenticationLevel: authentication.OneFactor},
|
||||||
|
"http://myapp.notgood.com",
|
||||||
|
fasthttp.StatusOK,
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckSafeRedirection_UnsafeRedirection(t *testing.T) {
|
for _, tc := range testCases {
|
||||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
Username: "john",
|
mock := mocks.NewMockAutheliaCtxWithUserSession(t, tc.userSession)
|
||||||
AuthenticationLevel: authentication.OneFactor,
|
|
||||||
})
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
|
||||||
|
|
||||||
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
||||||
URI: "http://myapp.com",
|
URI: tc.have,
|
||||||
})
|
})
|
||||||
|
|
||||||
CheckSafeRedirectionPOST(mock.Ctx)
|
CheckSafeRedirectionPOST(mock.Ctx)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expected, mock.Ctx.Response.StatusCode())
|
||||||
|
|
||||||
|
if tc.expected == fasthttp.StatusOK {
|
||||||
mock.Assert200OK(t, checkURIWithinDomainResponseBody{
|
mock.Assert200OK(t, checkURIWithinDomainResponseBody{
|
||||||
OK: false,
|
OK: tc.ok,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckSafeRedirection_SafeRedirection(t *testing.T) {
|
|
||||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
|
||||||
Username: "john",
|
|
||||||
AuthenticationLevel: authentication.OneFactor,
|
|
||||||
})
|
|
||||||
defer mock.Close()
|
|
||||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
|
||||||
|
|
||||||
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
|
||||||
URI: "https://myapp.example.com",
|
|
||||||
})
|
|
||||||
|
|
||||||
CheckSafeRedirectionPOST(mock.Ctx)
|
|
||||||
mock.Assert200OK(t, checkURIWithinDomainResponseBody{
|
|
||||||
OK: true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldFailOnInvalidBody(t *testing.T) {
|
func TestShouldFailOnInvalidBody(t *testing.T) {
|
||||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
||||||
|
@ -70,7 +84,7 @@ func TestShouldFailOnInvalidBody(t *testing.T) {
|
||||||
AuthenticationLevel: authentication.OneFactor,
|
AuthenticationLevel: authentication.OneFactor,
|
||||||
})
|
})
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
mock.Ctx.Configuration.Session.Domain = exampleDotCom
|
||||||
|
|
||||||
mock.SetRequestBody(t, "not a valid json")
|
mock.SetRequestBody(t, "not a valid json")
|
||||||
|
|
||||||
|
@ -84,7 +98,7 @@ func TestShouldFailOnInvalidURL(t *testing.T) {
|
||||||
AuthenticationLevel: authentication.OneFactor,
|
AuthenticationLevel: authentication.OneFactor,
|
||||||
})
|
})
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
mock.Ctx.Configuration.Session.Domain = exampleDotCom
|
||||||
|
|
||||||
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
||||||
URI: "https//invalid-url",
|
URI: "https//invalid-url",
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/regulation"
|
"github.com/authelia/authelia/v4/internal/regulation"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
|
@ -72,7 +71,25 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession := ctx.GetSession()
|
// TODO: write tests.
|
||||||
|
provider, err := ctx.GetSessionProvider()
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.Errorf("%s", err)
|
||||||
|
|
||||||
|
respondUnauthorized(ctx, messageAuthenticationFailed)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userSession, err := provider.GetSession(ctx.RequestCtx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.Errorf("%s", err)
|
||||||
|
|
||||||
|
respondUnauthorized(ctx, messageAuthenticationFailed)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
newSession := session.NewDefaultUserSession()
|
newSession := session.NewDefaultUserSession()
|
||||||
|
|
||||||
// Reset all values from previous session except OIDC workflow before regenerating the cookie.
|
// Reset all values from previous session except OIDC workflow before regenerating the cookie.
|
||||||
|
@ -84,7 +101,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx); err != nil {
|
if err = ctx.RegenerateSession(); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthType1FA, bodyJSON.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthType1FA, bodyJSON.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageAuthenticationFailed)
|
respondUnauthorized(ctx, messageAuthenticationFailed)
|
||||||
|
@ -93,11 +110,11 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if bodyJSON.KeepMeLoggedIn can be deref'd and derive the value based on the configuration and JSON data.
|
// Check if bodyJSON.KeepMeLoggedIn can be deref'd and derive the value based on the configuration and JSON data.
|
||||||
keepMeLoggedIn := ctx.Providers.SessionProvider.RememberMe != schema.RememberMeDisabled && bodyJSON.KeepMeLoggedIn != nil && *bodyJSON.KeepMeLoggedIn
|
keepMeLoggedIn := !provider.Config.DisableRememberMe && bodyJSON.KeepMeLoggedIn != nil && *bodyJSON.KeepMeLoggedIn
|
||||||
|
|
||||||
// Set the cookie to expire if remember me is enabled and the user has asked us to.
|
// Set the cookie to expire if remember me is enabled and the user has asked us to.
|
||||||
if keepMeLoggedIn {
|
if keepMeLoggedIn {
|
||||||
err = ctx.Providers.SessionProvider.UpdateExpiration(ctx.RequestCtx, ctx.Providers.SessionProvider.RememberMe)
|
err = provider.UpdateExpiration(ctx.RequestCtx, provider.Config.RememberMe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionSave, "updated expiration", regulation.AuthType1FA, bodyJSON.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionSave, "updated expiration", regulation.AuthType1FA, bodyJSON.Username, err)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type logoutBody struct {
|
type logoutBody struct {
|
||||||
|
@ -26,14 +25,14 @@ func LogoutPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
ctx.Error(fmt.Errorf("unable to parse body during logout: %s", err), messageOperationFailed)
|
ctx.Error(fmt.Errorf("unable to parse body during logout: %s", err), messageOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx)
|
err = ctx.DestroySession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("unable to destroy session during logout: %s", err), messageOperationFailed)
|
ctx.Error(fmt.Errorf("unable to destroy session during logout: %s", err), messageOperationFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectionURL, err := url.ParseRequestURI(body.TargetURL)
|
redirectionURL, err := url.ParseRequestURI(body.TargetURL)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
responseBody.SafeTargetURL = utils.IsURISafeRedirection(redirectionURL, ctx.Configuration.Session.Domain)
|
responseBody.SafeTargetURL = ctx.IsSafeRedirectionTargetURI(redirectionURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
if body.TargetURL != "" {
|
if body.TargetURL != "" {
|
||||||
|
|
|
@ -111,5 +111,9 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if requester.GetResponseMode() == oidc.ResponseModeFormPost {
|
||||||
|
ctx.SetUserValueBytes(middlewares.UserValueKeyFormPost, true)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Providers.OpenIDConnect.WriteAuthorizeResponse(ctx, rw, requester, responder)
|
ctx.Providers.OpenIDConnect.WriteAuthorizeResponse(ctx, rw, requester, responder)
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,7 +246,7 @@ func HandleAutoSelection(ctx *middlewares.AutheliaCtx, devices []DuoDevice, user
|
||||||
func HandleAllow(ctx *middlewares.AutheliaCtx, bodyJSON *bodySignDuoRequest) {
|
func HandleAllow(ctx *middlewares.AutheliaCtx, bodyJSON *bodySignDuoRequest) {
|
||||||
userSession := ctx.GetSession()
|
userSession := ctx.GetSession()
|
||||||
|
|
||||||
err := ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
err := ctx.RegenerateSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeDuo, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeDuo, userSession.Username, err)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/duo"
|
"github.com/authelia/authelia/v4/internal/duo"
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
|
@ -579,6 +580,18 @@ func (s *SecondFactorDuoPostSuite) TestShouldNotReturnRedirectURL() {
|
||||||
func (s *SecondFactorDuoPostSuite) TestShouldRedirectUserToSafeTargetURL() {
|
func (s *SecondFactorDuoPostSuite) TestShouldRedirectUserToSafeTargetURL() {
|
||||||
duoMock := mocks.NewMockAPI(s.mock.Ctrl)
|
duoMock := mocks.NewMockAPI(s.mock.Ctrl)
|
||||||
|
|
||||||
|
s.mock.Ctx.Configuration.Session.Cookies = []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: "example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: "mydomain.local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
s.mock.StorageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadPreferredDuoDevice(s.mock.Ctx, "john").
|
LoadPreferredDuoDevice(s.mock.Ctx, "john").
|
||||||
Return(&model.DuoDevice{ID: 1, Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}, nil)
|
Return(&model.DuoDevice{ID: 1, Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}, nil)
|
||||||
|
|
|
@ -50,7 +50,7 @@ func TimeBasedOneTimePasswordPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx); err != nil {
|
if err = ctx.RegenerateSession(); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeTOTP, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeTOTP, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
"github.com/authelia/authelia/v4/internal/regulation"
|
"github.com/authelia/authelia/v4/internal/regulation"
|
||||||
|
@ -143,6 +144,18 @@ func (s *HandlerSignTOTPSuite) TestShouldNotReturnRedirectURL() {
|
||||||
|
|
||||||
func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToSafeTargetURL() {
|
func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToSafeTargetURL() {
|
||||||
config := model.TOTPConfiguration{ID: 1, Username: "john", Digits: 6, Secret: []byte("secret"), Period: 30, Algorithm: "SHA1"}
|
config := model.TOTPConfiguration{ID: 1, Username: "john", Digits: 6, Secret: []byte("secret"), Period: 30, Algorithm: "SHA1"}
|
||||||
|
s.mock.Ctx.Configuration.Session.Cookies = []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: "example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Domain: "mydomain.local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
s.mock.StorageMock.EXPECT().
|
s.mock.StorageMock.EXPECT().
|
||||||
LoadTOTPConfiguration(s.mock.Ctx, gomock.Any()).
|
LoadTOTPConfiguration(s.mock.Ctx, gomock.Any()).
|
||||||
|
|
|
@ -44,7 +44,7 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
||||||
extensions := map[string]any{}
|
extensions := map[string]any{}
|
||||||
|
|
||||||
if user.HasFIDOU2F() {
|
if user.HasFIDOU2F() {
|
||||||
extensions["appid"] = w.Config.RPOrigin
|
extensions["appid"] = w.Config.RPOrigins[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(extensions) != 0 {
|
if len(extensions) != 0 {
|
||||||
|
@ -171,7 +171,7 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx); err != nil {
|
if err = ctx.RegenerateSession(); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeWebauthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeWebauthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
|
@ -259,9 +259,11 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
sessionConfig := mock.Ctx.Configuration.Session
|
||||||
|
|
||||||
if resp.config != nil {
|
if resp.config != nil {
|
||||||
mock.Ctx.Configuration = *resp.config
|
mock.Ctx.Configuration = *resp.config
|
||||||
|
mock.Ctx.Configuration.Session = sessionConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the initial user session.
|
// Set the initial user session.
|
||||||
|
|
|
@ -19,14 +19,6 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isURLUnderProtectedDomain(url *url.URL, domain string) bool {
|
|
||||||
return strings.HasSuffix(url.Hostname(), domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSchemeHTTPS(url *url.URL) bool {
|
|
||||||
return url.Scheme == "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSchemeWSS(url *url.URL) bool {
|
func isSchemeWSS(url *url.URL) bool {
|
||||||
return url.Scheme == "wss"
|
return url.Scheme == "wss"
|
||||||
}
|
}
|
||||||
|
@ -54,7 +46,7 @@ func parseBasicAuth(header []byte, auth string) (username, password string, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// isTargetURLAuthorized check whether the given user is authorized to access the resource.
|
// isTargetURLAuthorized check whether the given user is authorized to access the resource.
|
||||||
func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.URL,
|
func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL *url.URL,
|
||||||
username string, userGroups []string, clientIP net.IP, method []byte, authLevel authentication.Level) authorizationMatching {
|
username string, userGroups []string, clientIP net.IP, method []byte, authLevel authentication.Level) authorizationMatching {
|
||||||
hasSubject, level := authorizer.GetRequiredLevel(
|
hasSubject, level := authorizer.GetRequiredLevel(
|
||||||
authorization.Subject{
|
authorization.Subject{
|
||||||
|
@ -62,7 +54,7 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
|
||||||
Groups: userGroups,
|
Groups: userGroups,
|
||||||
IP: clientIP,
|
IP: clientIP,
|
||||||
},
|
},
|
||||||
authorization.NewObjectRaw(&targetURL, method))
|
authorization.NewObjectRaw(targetURL, method))
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case level == authorization.Bypass:
|
case level == authorization.Bypass:
|
||||||
|
@ -128,13 +120,18 @@ func setForwardedHeaders(headers *fasthttp.ResponseHeader, username, name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSessionInactiveTooLong(ctx *middlewares.AutheliaCtx, userSession *session.UserSession, isUserAnonymous bool) (isInactiveTooLong bool) {
|
func isSessionInactiveTooLong(ctx *middlewares.AutheliaCtx, userSession *session.UserSession, isUserAnonymous bool) (isInactiveTooLong bool) {
|
||||||
if userSession.KeepMeLoggedIn || isUserAnonymous || int64(ctx.Providers.SessionProvider.Inactivity.Seconds()) == 0 {
|
domainSession, err := ctx.GetSessionProvider()
|
||||||
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
isInactiveTooLong = time.Unix(userSession.LastActivity, 0).Add(ctx.Providers.SessionProvider.Inactivity).Before(ctx.Clock.Now())
|
if userSession.KeepMeLoggedIn || isUserAnonymous || int64(domainSession.Config.Inactivity.Seconds()) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Logger.Tracef("Inactivity report for user '%s'. Current Time: %d, Last Activity: %d, Maximum Inactivity: %d.", userSession.Username, ctx.Clock.Now().Unix(), userSession.LastActivity, int(ctx.Providers.SessionProvider.Inactivity.Seconds()))
|
isInactiveTooLong = time.Unix(userSession.LastActivity, 0).Add(domainSession.Config.Inactivity).Before(ctx.Clock.Now())
|
||||||
|
|
||||||
|
ctx.Logger.Tracef("Inactivity report for user '%s'. Current Time: %d, Last Activity: %d, Maximum Inactivity: %d.", userSession.Username, ctx.Clock.Now().Unix(), userSession.LastActivity, int(domainSession.Config.Inactivity.Seconds()))
|
||||||
|
|
||||||
return isInactiveTooLong
|
return isInactiveTooLong
|
||||||
}
|
}
|
||||||
|
@ -151,7 +148,7 @@ func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userS
|
||||||
|
|
||||||
if isSessionInactiveTooLong(ctx, userSession, isUserAnonymous) {
|
if isSessionInactiveTooLong(ctx, userSession, isUserAnonymous) {
|
||||||
// Destroy the session a new one will be regenerated on next request.
|
// Destroy the session a new one will be regenerated on next request.
|
||||||
if err = ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx); err != nil {
|
if err = ctx.DestroySession(); err != nil {
|
||||||
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("unable to destroy session for user '%s' after the session has been inactive too long: %w", userSession.Username, err)
|
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("unable to destroy session for user '%s' after the session has been inactive too long: %w", userSession.Username, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +159,7 @@ func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userS
|
||||||
|
|
||||||
if err = verifySessionHasUpToDateProfile(ctx, targetURL, userSession, refreshProfile, refreshProfileInterval); err != nil {
|
if err = verifySessionHasUpToDateProfile(ctx, targetURL, userSession, refreshProfile, refreshProfileInterval); err != nil {
|
||||||
if err == authentication.ErrUserNotFound {
|
if err == authentication.ErrUserNotFound {
|
||||||
if err = ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx); err != nil {
|
if err = ctx.DestroySession(); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to destroy user session after provider refresh didn't find the user: %v", err)
|
ctx.Logger.Errorf("Unable to destroy user session after provider refresh didn't find the user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +174,7 @@ func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userS
|
||||||
return userSession.Username, userSession.DisplayName, userSession.Groups, userSession.Emails, userSession.AuthenticationLevel, nil
|
return userSession.Username, userSession.DisplayName, userSession.Groups, userSession.Emails, userSession.AuthenticationLevel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, isBasicAuth bool, username string, method []byte) {
|
func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, cookieDomain string, isBasicAuth bool, username string, method []byte) {
|
||||||
var (
|
var (
|
||||||
statusCode int
|
statusCode int
|
||||||
friendlyUsername string
|
friendlyUsername string
|
||||||
|
@ -211,8 +208,8 @@ func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, is
|
||||||
redirectionURL := ctxGetPortalURL(ctx)
|
redirectionURL := ctxGetPortalURL(ctx)
|
||||||
|
|
||||||
if redirectionURL != nil {
|
if redirectionURL != nil {
|
||||||
if !utils.IsURISafeRedirection(redirectionURL, ctx.Configuration.Session.Domain) {
|
if !utils.IsURISafeRedirection(redirectionURL, cookieDomain) {
|
||||||
ctx.Logger.Errorf("Configured Portal URL '%s' does not appear to be able to write cookies for the '%s' domain", redirectionURL, ctx.Configuration.Session.Domain)
|
ctx.Logger.Errorf("Configured Portal URL '%s' does not appear to be able to write cookies for the '%s' domain", redirectionURL, cookieDomain)
|
||||||
|
|
||||||
ctx.ReplyUnauthorized()
|
ctx.ReplyUnauthorized()
|
||||||
|
|
||||||
|
@ -414,6 +411,7 @@ func verifyAuth(ctx *middlewares.AutheliaCtx, targetURL *url.URL, refreshProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession := ctx.GetSession()
|
userSession := ctx.GetSession()
|
||||||
|
|
||||||
if username, name, groups, emails, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession, refreshProfile, refreshProfileInterval); err != nil {
|
if username, name, groups, emails, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession, refreshProfile, refreshProfileInterval); err != nil {
|
||||||
return isBasicAuth, username, name, groups, emails, authLevel, err
|
return isBasicAuth, username, name, groups, emails, authLevel, err
|
||||||
}
|
}
|
||||||
|
@ -422,7 +420,7 @@ func verifyAuth(ctx *middlewares.AutheliaCtx, targetURL *url.URL, refreshProfile
|
||||||
if sessionUsername != nil && !strings.EqualFold(string(sessionUsername), username) {
|
if sessionUsername != nil && !strings.EqualFold(string(sessionUsername), username) {
|
||||||
ctx.Logger.Warnf("Possible cookie hijack or attempt to bypass security detected destroying the session and sending 401 response")
|
ctx.Logger.Warnf("Possible cookie hijack or attempt to bypass security detected destroying the session and sending 401 response")
|
||||||
|
|
||||||
if err = ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx); err != nil {
|
if err = ctx.DestroySession(); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to destroy user session after handler could not match them to their %s header: %s", headerSessionUsername, err)
|
ctx.Logger.Errorf("Unable to destroy user session after handler could not match them to their %s header: %s", headerSessionUsername, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,7 +445,7 @@ func VerifyGET(cfg schema.AuthenticationBackend) middlewares.RequestHandler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isSchemeHTTPS(targetURL) && !isSchemeWSS(targetURL) {
|
if !utils.IsURISecure(targetURL) {
|
||||||
ctx.Logger.Errorf("Scheme of target URL %s must be secure since cookies are "+
|
ctx.Logger.Errorf("Scheme of target URL %s must be secure since cookies are "+
|
||||||
"only transported over a secure connection for security reasons", targetURL.String())
|
"only transported over a secure connection for security reasons", targetURL.String())
|
||||||
ctx.ReplyUnauthorized()
|
ctx.ReplyUnauthorized()
|
||||||
|
@ -455,14 +453,32 @@ func VerifyGET(cfg schema.AuthenticationBackend) middlewares.RequestHandler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isURLUnderProtectedDomain(targetURL, ctx.Configuration.Session.Domain) {
|
cookieDomain := ctx.GetTargetURICookieDomain(targetURL)
|
||||||
ctx.Logger.Errorf("Target URL %s is not under the protected domain %s",
|
|
||||||
targetURL.String(), ctx.Configuration.Session.Domain)
|
if cookieDomain == "" {
|
||||||
|
l := len(ctx.Configuration.Session.Cookies)
|
||||||
|
|
||||||
|
if l == 1 {
|
||||||
|
ctx.Logger.Errorf("Target URL '%s' was not detected as a match to the '%s' session cookie domain",
|
||||||
|
targetURL.String(), ctx.Configuration.Session.Cookies[0].Domain)
|
||||||
|
} else {
|
||||||
|
domains := make([]string, 0, len(ctx.Configuration.Session.Cookies))
|
||||||
|
|
||||||
|
for i, domain := range ctx.Configuration.Session.Cookies {
|
||||||
|
domains[i] = domain.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Logger.Errorf("Target URL '%s' was not detected as a match to any of the '%s' session cookie domains",
|
||||||
|
targetURL.String(), strings.Join(domains, "', '"))
|
||||||
|
}
|
||||||
|
|
||||||
ctx.ReplyUnauthorized()
|
ctx.ReplyUnauthorized()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Logger.Debugf("Target URL '%s' was detected as a match to the '%s' session cookie domain", targetURL.String(), cookieDomain)
|
||||||
|
|
||||||
method := ctx.XForwardedMethod()
|
method := ctx.XForwardedMethod()
|
||||||
isBasicAuth, username, name, groups, emails, authLevel, err := verifyAuth(ctx, targetURL, refreshProfile, refreshProfileInterval)
|
isBasicAuth, username, name, groups, emails, authLevel, err := verifyAuth(ctx, targetURL, refreshProfile, refreshProfileInterval)
|
||||||
|
|
||||||
|
@ -474,12 +490,12 @@ func VerifyGET(cfg schema.AuthenticationBackend) middlewares.RequestHandler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUnauthorized(ctx, targetURL, isBasicAuth, username, method)
|
handleUnauthorized(ctx, targetURL, cookieDomain, isBasicAuth, username, method)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authorized := isTargetURLAuthorized(ctx.Providers.Authorizer, *targetURL, username,
|
authorized := isTargetURLAuthorized(ctx.Providers.Authorizer, targetURL, username,
|
||||||
groups, ctx.RemoteIP(), method, authLevel)
|
groups, ctx.RemoteIP(), method, authLevel)
|
||||||
|
|
||||||
switch authorized {
|
switch authorized {
|
||||||
|
@ -487,7 +503,7 @@ func VerifyGET(cfg schema.AuthenticationBackend) middlewares.RequestHandler {
|
||||||
ctx.Logger.Infof("Access to %s is forbidden to user %s", targetURL.String(), username)
|
ctx.Logger.Infof("Access to %s is forbidden to user %s", targetURL.String(), username)
|
||||||
ctx.ReplyForbidden()
|
ctx.ReplyForbidden()
|
||||||
case NotAuthorized:
|
case NotAuthorized:
|
||||||
handleUnauthorized(ctx, targetURL, isBasicAuth, username, method)
|
handleUnauthorized(ctx, targetURL, cookieDomain, isBasicAuth, username, method)
|
||||||
case Authorized:
|
case Authorized:
|
||||||
setForwardedHeaders(&ctx.Response.Header, username, name, groups, emails)
|
setForwardedHeaders(&ctx.Response.Header, username, name, groups, emails)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,8 @@ func TestShouldRaiseWhenTargetUrlIsMalformed(t *testing.T) {
|
||||||
|
|
||||||
func TestShouldRaiseWhenNoHeaderProvidedToDetectTargetURL(t *testing.T) {
|
func TestShouldRaiseWhenNoHeaderProvidedToDetectTargetURL(t *testing.T) {
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
mock.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
_, err := mock.Ctx.GetOriginalURL()
|
_, err := mock.Ctx.GetOriginalURL()
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
@ -53,6 +55,7 @@ func TestShouldRaiseWhenNoXForwardedHostHeaderProvidedToDetectTargetURL(t *testi
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
|
mock.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
||||||
_, err := mock.Ctx.GetOriginalURL()
|
_, err := mock.Ctx.GetOriginalURL()
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
@ -162,7 +165,7 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) {
|
||||||
username = testUsername
|
username = testUsername
|
||||||
}
|
}
|
||||||
|
|
||||||
matching := isTargetURLAuthorized(authorizer, *u, username, []string{}, net.ParseIP("127.0.0.1"), []byte("GET"), rule.AuthLevel)
|
matching := isTargetURLAuthorized(authorizer, u, username, []string{}, net.ParseIP("127.0.0.1"), []byte("GET"), rule.AuthLevel)
|
||||||
assert.Equal(t, rule.ExpectedMatching, matching, "policy=%s, authLevel=%v, expected=%v, actual=%v",
|
assert.Equal(t, rule.ExpectedMatching, matching, "policy=%s, authLevel=%v, expected=%v, actual=%v",
|
||||||
rule.Policy, rule.AuthLevel, rule.ExpectedMatching, matching)
|
rule.Policy, rule.AuthLevel, rule.ExpectedMatching, matching)
|
||||||
}
|
}
|
||||||
|
@ -510,7 +513,6 @@ func TestShouldNotCrashOnEmptyEmail(t *testing.T) {
|
||||||
userSession.AuthenticationLevel = authentication.OneFactor
|
userSession.AuthenticationLevel = authentication.OneFactor
|
||||||
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
||||||
|
|
||||||
fmt.Printf("Time is %v\n", userSession.RefreshTTL)
|
|
||||||
err := mock.Ctx.SaveSession(userSession)
|
err := mock.Ctx.SaveSession(userSession)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -663,32 +665,33 @@ func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
|
||||||
{"https://deny.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 403},
|
{"https://deny.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 403},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for i, tc := range testCases {
|
||||||
testCase := testCase
|
t.Run(tc.String(), func(t *testing.T) {
|
||||||
t.Run(testCase.String(), func(t *testing.T) {
|
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
mock.Clock.Set(time.Now())
|
mock.Clock.Set(time.Now())
|
||||||
|
|
||||||
|
mock.Ctx.Request.Header.Set("X-Original-URL", tc.URL)
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
userSession.Username = testCase.Username
|
userSession.Username = tc.Username
|
||||||
userSession.Emails = testCase.Emails
|
userSession.Emails = tc.Emails
|
||||||
userSession.AuthenticationLevel = testCase.AuthenticationLevel
|
userSession.AuthenticationLevel = tc.AuthenticationLevel
|
||||||
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
||||||
|
|
||||||
err := mock.Ctx.SaveSession(userSession)
|
err := mock.Ctx.SaveSession(userSession)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Original-URL", testCase.URL)
|
|
||||||
|
|
||||||
VerifyGET(verifyGetCfg)(mock.Ctx)
|
VerifyGET(verifyGetCfg)(mock.Ctx)
|
||||||
expStatus, actualStatus := testCase.ExpectedStatusCode, mock.Ctx.Response.StatusCode()
|
expStatus, actualStatus := tc.ExpectedStatusCode, mock.Ctx.Response.StatusCode()
|
||||||
assert.Equal(t, expStatus, actualStatus, "URL=%s -> AuthLevel=%d, StatusCode=%d != ExpectedStatusCode=%d",
|
assert.Equal(t, expStatus, actualStatus, "URL=%s -> AuthLevel=%d, StatusCode=%d != ExpectedStatusCode=%d",
|
||||||
testCase.URL, testCase.AuthenticationLevel, actualStatus, expStatus)
|
tc.URL, tc.AuthenticationLevel, actualStatus, expStatus)
|
||||||
|
|
||||||
if testCase.ExpectedStatusCode == 200 && testCase.Username != "" {
|
fmt.Println(i)
|
||||||
assert.Equal(t, []byte(testCase.Username), mock.Ctx.Response.Header.Peek("Remote-User"))
|
if tc.ExpectedStatusCode == 200 && tc.Username != "" {
|
||||||
|
assert.Equal(t, tc.ExpectedStatusCode, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, []byte(tc.Username), mock.Ctx.Response.Header.Peek("Remote-User"))
|
||||||
assert.Equal(t, []byte("john.doe@example.com"), mock.Ctx.Response.Header.Peek("Remote-Email"))
|
assert.Equal(t, []byte("john.doe@example.com"), mock.Ctx.Response.Header.Peek("Remote-Email"))
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek("Remote-User"))
|
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek("Remote-User"))
|
||||||
|
@ -706,10 +709,12 @@ func TestShouldDestroySessionWhenInactiveForTooLong(t *testing.T) {
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
past := clock.Now().Add(-1 * time.Hour)
|
past := clock.Now().Add(-1 * time.Hour)
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
// Reload the session provider since the configuration is indirect.
|
// Reload the session provider since the configuration is indirect.
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
||||||
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
assert.Equal(t, time.Second*10, mock.Ctx.Configuration.Session.Cookies[0].Inactivity)
|
||||||
|
|
||||||
|
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
userSession.Username = testUsername
|
userSession.Username = testUsername
|
||||||
|
@ -719,8 +724,6 @@ func TestShouldDestroySessionWhenInactiveForTooLong(t *testing.T) {
|
||||||
err := mock.Ctx.SaveSession(userSession)
|
err := mock.Ctx.SaveSession(userSession)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
|
|
||||||
|
|
||||||
VerifyGET(verifyGetCfg)(mock.Ctx)
|
VerifyGET(verifyGetCfg)(mock.Ctx)
|
||||||
|
|
||||||
// The session has been destroyed.
|
// The session has been destroyed.
|
||||||
|
@ -739,10 +742,10 @@ func TestShouldDestroySessionWhenInactiveForTooLongUsingDurationNotation(t *test
|
||||||
clock := utils.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = time.Second * 10
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = time.Second * 10
|
||||||
// Reload the session provider since the configuration is indirect.
|
// Reload the session provider since the configuration is indirect.
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
||||||
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
assert.Equal(t, time.Second*10, mock.Ctx.Configuration.Session.Cookies[0].Inactivity)
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
userSession.Username = testUsername
|
userSession.Username = testUsername
|
||||||
|
@ -768,7 +771,7 @@ func TestShouldKeepSessionWhenUserCheckedRememberMeAndIsInactiveForTooLong(t *te
|
||||||
|
|
||||||
mock.Clock.Set(time.Now())
|
mock.Clock.Set(time.Now())
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
userSession.Username = testUsername
|
userSession.Username = testUsername
|
||||||
|
@ -800,7 +803,7 @@ func TestShouldKeepSessionWhenInactivityTimeoutHasNotBeenExceeded(t *testing.T)
|
||||||
|
|
||||||
mock.Clock.Set(time.Now())
|
mock.Clock.Set(time.Now())
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
past := mock.Clock.Now().Add(-1 * time.Hour)
|
past := mock.Clock.Now().Add(-1 * time.Hour)
|
||||||
|
|
||||||
|
@ -836,10 +839,10 @@ func TestShouldRedirectWhenSessionInactiveForTooLongAndRDParamProvided(t *testin
|
||||||
clock := utils.TestingClock{}
|
clock := utils.TestingClock{}
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
// Reload the session provider since the configuration is indirect.
|
// Reload the session provider since the configuration is indirect.
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
||||||
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
assert.Equal(t, time.Second*10, mock.Ctx.Configuration.Session.Cookies[0].Inactivity)
|
||||||
|
|
||||||
past := clock.Now().Add(-1 * time.Hour)
|
past := clock.Now().Add(-1 * time.Hour)
|
||||||
|
|
||||||
|
@ -899,7 +902,7 @@ func TestShouldUpdateInactivityTimestampEvenWhenHittingForbiddenResources(t *tes
|
||||||
|
|
||||||
mock.Clock.Set(time.Now())
|
mock.Clock.Set(time.Now())
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
|
|
||||||
past := mock.Clock.Now().Add(-1 * time.Hour)
|
past := mock.Clock.Now().Add(-1 * time.Hour)
|
||||||
|
|
||||||
|
@ -974,47 +977,6 @@ func TestShouldURLEncodeRedirectionHeader(t *testing.T) {
|
||||||
string(mock.Ctx.Response.Body()))
|
string(mock.Ctx.Response.Body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsDomainProtected(t *testing.T) {
|
|
||||||
GetURL := func(u string) *url.URL {
|
|
||||||
x, err := url.ParseRequestURI(u)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(t, isURLUnderProtectedDomain(
|
|
||||||
GetURL("http://mytest.example.com/abc/?query=abc"), "example.com"))
|
|
||||||
|
|
||||||
assert.True(t, isURLUnderProtectedDomain(
|
|
||||||
GetURL("http://example.com/abc/?query=abc"), "example.com"))
|
|
||||||
|
|
||||||
assert.True(t, isURLUnderProtectedDomain(
|
|
||||||
GetURL("https://mytest.example.com/abc/?query=abc"), "example.com"))
|
|
||||||
|
|
||||||
// Cookies readable by a service on a machine is also readable by a service on the same machine
|
|
||||||
// with a different port as mentioned in https://tools.ietf.org/html/rfc6265#section-8.5.
|
|
||||||
assert.True(t, isURLUnderProtectedDomain(
|
|
||||||
GetURL("https://mytest.example.com:8080/abc/?query=abc"), "example.com"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSchemeIsHTTPS(t *testing.T) {
|
|
||||||
GetURL := func(u string) *url.URL {
|
|
||||||
x, err := url.ParseRequestURI(u)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.False(t, isSchemeHTTPS(
|
|
||||||
GetURL("http://mytest.example.com/abc/?query=abc")))
|
|
||||||
assert.False(t, isSchemeHTTPS(
|
|
||||||
GetURL("ws://mytest.example.com/abc/?query=abc")))
|
|
||||||
assert.False(t, isSchemeHTTPS(
|
|
||||||
GetURL("wss://mytest.example.com/abc/?query=abc")))
|
|
||||||
assert.True(t, isSchemeHTTPS(
|
|
||||||
GetURL("https://mytest.example.com/abc/?query=abc")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSchemeIsWSS(t *testing.T) {
|
func TestSchemeIsWSS(t *testing.T) {
|
||||||
GetURL := func(u string) *url.URL {
|
GetURL := func(u string) *url.URL {
|
||||||
x, err := url.ParseRequestURI(u)
|
x, err := url.ParseRequestURI(u)
|
||||||
|
@ -1435,10 +1397,10 @@ func TestShouldNotRedirectRequestsForBypassACLWhenInactiveForTooLong(t *testing.
|
||||||
clock.Set(time.Now())
|
clock.Set(time.Now())
|
||||||
past := clock.Now().Add(-1 * time.Hour)
|
past := clock.Now().Add(-1 * time.Hour)
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||||
// Reload the session provider since the configuration is indirect.
|
// Reload the session provider since the configuration is indirect.
|
||||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
||||||
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
assert.Equal(t, time.Second*10, mock.Ctx.Configuration.Session.Cookies[0].Inactivity)
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
userSession.Username = testUsername
|
userSession.Username = testUsername
|
||||||
|
@ -1527,7 +1489,7 @@ func TestIsSessionInactiveTooLong(t *testing.T) {
|
||||||
|
|
||||||
defer ctx.Close()
|
defer ctx.Close()
|
||||||
|
|
||||||
ctx.Ctx.Configuration.Session.Inactivity = tc.inactivity
|
ctx.Ctx.Configuration.Session.Cookies[0].Inactivity = tc.inactivity
|
||||||
ctx.Ctx.Providers.SessionProvider = session.NewProvider(ctx.Ctx.Configuration.Session, nil)
|
ctx.Ctx.Providers.SessionProvider = session.NewProvider(ctx.Ctx.Configuration.Session, nil)
|
||||||
|
|
||||||
ctx.Clock.Set(tc.now)
|
ctx.Clock.Set(tc.now)
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
"github.com/authelia/authelia/v4/internal/oidc"
|
"github.com/authelia/authelia/v4/internal/oidc"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handle1FAResponse handle the redirection upon 1FA authentication.
|
// Handle1FAResponse handle the redirection upon 1FA authentication.
|
||||||
|
@ -57,7 +56,7 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI, requestMethod st
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsURISafeRedirection(targetURL, ctx.Configuration.Session.Domain) {
|
if !ctx.IsSafeRedirectionTargetURI(targetURL) {
|
||||||
ctx.Logger.Debugf("Redirection URL %s is not safe", targetURI)
|
ctx.Logger.Debugf("Redirection URL %s is not safe", targetURI)
|
||||||
|
|
||||||
if !ctx.Providers.Authorizer.IsSecondFactorEnabled() && ctx.Configuration.DefaultRedirectionURL != "" {
|
if !ctx.Providers.Authorizer.IsSecondFactorEnabled() && ctx.Configuration.DefaultRedirectionURL != "" {
|
||||||
|
@ -98,14 +97,18 @@ func Handle2FAResponse(ctx *middlewares.AutheliaCtx, targetURI string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var safe bool
|
var (
|
||||||
|
parsedURI *url.URL
|
||||||
if safe, err = utils.IsURIStringSafeRedirection(targetURI, ctx.Configuration.Session.Domain); err != nil {
|
safe bool
|
||||||
ctx.Error(fmt.Errorf("unable to check target URL: %s", err), messageMFAValidationFailed)
|
)
|
||||||
|
|
||||||
|
if parsedURI, err = url.ParseRequestURI(targetURI); err != nil {
|
||||||
|
ctx.Error(fmt.Errorf("unable to determine if URI '%s' is safe to redirect to: failed to parse URI '%s': %w", targetURI, targetURI, err), messageMFAValidationFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
safe = ctx.IsSafeRedirectionTargetURI(parsedURI)
|
||||||
|
|
||||||
if safe {
|
if safe {
|
||||||
ctx.Logger.Debugf("Redirection URL %s is safe", targetURI)
|
ctx.Logger.Debugf("Redirection URL %s is safe", targetURI)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
@ -44,7 +45,7 @@ func newWebauthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error)
|
||||||
config := &webauthn.Config{
|
config := &webauthn.Config{
|
||||||
RPDisplayName: ctx.Configuration.Webauthn.DisplayName,
|
RPDisplayName: ctx.Configuration.Webauthn.DisplayName,
|
||||||
RPID: rpID,
|
RPID: rpID,
|
||||||
RPOrigin: origin,
|
RPOrigins: []string{origin},
|
||||||
RPIcon: "",
|
RPIcon: "",
|
||||||
|
|
||||||
AttestationPreference: ctx.Configuration.Webauthn.ConveyancePreference,
|
AttestationPreference: ctx.Configuration.Webauthn.ConveyancePreference,
|
||||||
|
@ -57,7 +58,7 @@ func newWebauthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error)
|
||||||
Timeout: int(ctx.Configuration.Webauthn.Timeout.Milliseconds()),
|
Timeout: int(ctx.Configuration.Webauthn.Timeout.Milliseconds()),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Tracef("Creating new Webauthn RP instance with ID %s and Origin %s", config.RPID, config.RPOrigin)
|
ctx.Logger.Tracef("Creating new Webauthn RP instance with ID %s and Origins %s", config.RPID, strings.Join(config.RPOrigins, ", "))
|
||||||
|
|
||||||
return webauthn.New(config)
|
return webauthn.New(config)
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,6 +146,7 @@ func TestWebauthnGetUserWithErr(t *testing.T) {
|
||||||
|
|
||||||
func TestWebauthnNewWebauthnShouldReturnErrWhenHeadersNotAvailable(t *testing.T) {
|
func TestWebauthnNewWebauthnShouldReturnErrWhenHeadersNotAvailable(t *testing.T) {
|
||||||
ctx := mocks.NewMockAutheliaCtx(t)
|
ctx := mocks.NewMockAutheliaCtx(t)
|
||||||
|
ctx.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||||
|
|
||||||
w, err := newWebauthn(ctx.Ctx)
|
w, err := newWebauthn(ctx.Ctx)
|
||||||
|
|
||||||
|
|
|
@ -238,9 +238,63 @@ func (ctx *AutheliaCtx) RootURLSlash() (issuerURL *url.URL) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTargetURICookieDomain returns the session provider for the targetURI domain.
|
||||||
|
func (ctx *AutheliaCtx) GetTargetURICookieDomain(targetURI *url.URL) string {
|
||||||
|
hostname := targetURI.Hostname()
|
||||||
|
|
||||||
|
for _, domain := range ctx.Configuration.Session.Cookies {
|
||||||
|
if utils.HasDomainSuffix(hostname, domain.Domain) {
|
||||||
|
return domain.Domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSafeRedirectionTargetURI returns true if the targetURI is within the scope of a cookie domain and secure.
|
||||||
|
func (ctx *AutheliaCtx) IsSafeRedirectionTargetURI(targetURI *url.URL) bool {
|
||||||
|
if !utils.IsURISecure(targetURI) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.GetTargetURICookieDomain(targetURI) != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCookieDomain returns the cookie domain for the current request.
|
||||||
|
func (ctx *AutheliaCtx) GetCookieDomain() (domain string, err error) {
|
||||||
|
var targetURI *url.URL
|
||||||
|
|
||||||
|
if targetURI, err = ctx.GetOriginalURL(); err != nil {
|
||||||
|
return "", fmt.Errorf("unable to retrieve cookie domain: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.GetTargetURICookieDomain(targetURI), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSessionProvider returns the session provider for the Request's domain.
|
||||||
|
func (ctx *AutheliaCtx) GetSessionProvider() (provider *session.Session, err error) {
|
||||||
|
var cookieDomain string
|
||||||
|
|
||||||
|
if cookieDomain, err = ctx.GetCookieDomain(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cookieDomain == "" {
|
||||||
|
return nil, fmt.Errorf("unable to retrieve domain session: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Providers.SessionProvider.Get(cookieDomain)
|
||||||
|
}
|
||||||
|
|
||||||
// GetSession return the user session. Any update will be saved in cache.
|
// GetSession return the user session. Any update will be saved in cache.
|
||||||
func (ctx *AutheliaCtx) GetSession() session.UserSession {
|
func (ctx *AutheliaCtx) GetSession() session.UserSession {
|
||||||
userSession, err := ctx.Providers.SessionProvider.GetSession(ctx.RequestCtx)
|
provider, err := ctx.GetSessionProvider()
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.Error("Unable to retrieve domain session")
|
||||||
|
return session.NewDefaultUserSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
userSession, err := provider.GetSession(ctx.RequestCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Error("Unable to retrieve user session")
|
ctx.Logger.Error("Unable to retrieve user session")
|
||||||
return session.NewDefaultUserSession()
|
return session.NewDefaultUserSession()
|
||||||
|
@ -251,7 +305,32 @@ func (ctx *AutheliaCtx) GetSession() session.UserSession {
|
||||||
|
|
||||||
// SaveSession save the content of the session.
|
// SaveSession save the content of the session.
|
||||||
func (ctx *AutheliaCtx) SaveSession(userSession session.UserSession) error {
|
func (ctx *AutheliaCtx) SaveSession(userSession session.UserSession) error {
|
||||||
return ctx.Providers.SessionProvider.SaveSession(ctx.RequestCtx, userSession)
|
provider, err := ctx.GetSessionProvider()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to save user session: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.SaveSession(ctx.RequestCtx, userSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegenerateSession regenerates user session.
|
||||||
|
func (ctx *AutheliaCtx) RegenerateSession() error {
|
||||||
|
provider, err := ctx.GetSessionProvider()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to regenerate user session: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.RegenerateSession(ctx.RequestCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroySession destroy user session.
|
||||||
|
func (ctx *AutheliaCtx) DestroySession() error {
|
||||||
|
provider, err := ctx.GetSessionProvider()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to destroy user session: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.DestroySession(ctx.RequestCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplyOK is a helper method to reply ok.
|
// ReplyOK is a helper method to reply ok.
|
||||||
|
|
|
@ -182,6 +182,8 @@ func TestShouldGetOriginalURLFromForwardedHeadersWithURI(t *testing.T) {
|
||||||
|
|
||||||
func TestShouldFallbackToNonXForwardedHeaders(t *testing.T) {
|
func TestShouldFallbackToNonXForwardedHeaders(t *testing.T) {
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
mock.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||||
|
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
||||||
|
@ -196,6 +198,8 @@ func TestShouldOnlyFallbackToNonXForwardedHeadersWhenNil(t *testing.T) {
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
|
mock.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||||
|
|
||||||
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
||||||
mock.Ctx.RequestCtx.Request.SetHost("localhost")
|
mock.Ctx.RequestCtx.Request.SetHost("localhost")
|
||||||
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "auth.example.com:1234")
|
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "auth.example.com:1234")
|
||||||
|
|
|
@ -54,7 +54,8 @@ var (
|
||||||
headerValueVaryWildcard = []byte("Accept-Encoding")
|
headerValueVaryWildcard = []byte("Accept-Encoding")
|
||||||
headerValueOriginWildcard = []byte("*")
|
headerValueOriginWildcard = []byte("*")
|
||||||
headerValueZero = []byte("0")
|
headerValueZero = []byte("0")
|
||||||
headerValueCSPNone = []byte("default-src 'none';")
|
headerValueCSPNone = []byte("default-src 'none'")
|
||||||
|
headerValueCSPNoneFormPost = []byte("default-src 'none'; script-src 'sha256-skflBqA90WuHvoczvimLdj49ExKdizFjX2Itd6xKZdU='")
|
||||||
|
|
||||||
headerValueNoSniff = []byte("nosniff")
|
headerValueNoSniff = []byte("nosniff")
|
||||||
headerValueStrictOriginCrossOrigin = []byte("strict-origin-when-cross-origin")
|
headerValueStrictOriginCrossOrigin = []byte("strict-origin-when-cross-origin")
|
||||||
|
@ -83,6 +84,9 @@ var (
|
||||||
// UserValueKeyBaseURL is the User Value key where we store the Base URL.
|
// UserValueKeyBaseURL is the User Value key where we store the Base URL.
|
||||||
UserValueKeyBaseURL = []byte("base_url")
|
UserValueKeyBaseURL = []byte("base_url")
|
||||||
|
|
||||||
|
// UserValueKeyFormPost is the User Value key where we indicate the form_post response mode.
|
||||||
|
UserValueKeyFormPost = []byte("form_post")
|
||||||
|
|
||||||
headerSeparator = []byte(", ")
|
headerSeparator = []byte(", ")
|
||||||
|
|
||||||
contentTypeTextPlain = []byte("text/plain; charset=utf-8")
|
contentTypeTextPlain = []byte("text/plain; charset=utf-8")
|
||||||
|
|
|
@ -26,6 +26,22 @@ func SecurityHeadersCSPNone(next fasthttp.RequestHandler) fasthttp.RequestHandle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecurityHeadersCSPNoneOpenIDConnect middleware adds the Content-Security-Policy header with the value
|
||||||
|
// "default-src 'none'" except in special circumstances.
|
||||||
|
func SecurityHeadersCSPNoneOpenIDConnect(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
|
return func(ctx *fasthttp.RequestCtx) {
|
||||||
|
ctx.SetUserValueBytes(UserValueKeyFormPost, false)
|
||||||
|
|
||||||
|
next(ctx)
|
||||||
|
|
||||||
|
if modeFormPost, ok := ctx.UserValueBytes(UserValueKeyFormPost).(bool); ok && modeFormPost {
|
||||||
|
ctx.Response.Header.SetBytesKV(headerContentSecurityPolicy, headerValueCSPNoneFormPost)
|
||||||
|
} else {
|
||||||
|
ctx.Response.Header.SetBytesKV(headerContentSecurityPolicy, headerValueCSPNone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SecurityHeadersNoStore middleware adds the Pragma no-cache and Cache-Control no-store headers.
|
// SecurityHeadersNoStore middleware adds the Pragma no-cache and Cache-Control no-store headers.
|
||||||
func SecurityHeadersNoStore(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
func SecurityHeadersNoStore(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
return func(ctx *fasthttp.RequestCtx) {
|
return func(ctx *fasthttp.RequestCtx) {
|
||||||
|
|
|
@ -51,9 +51,25 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
||||||
mockAuthelia.Clock.Set(datetime)
|
mockAuthelia.Clock.Set(datetime)
|
||||||
|
|
||||||
config := schema.Configuration{}
|
config := schema.Configuration{}
|
||||||
config.Session.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration
|
config.Session.Cookies = []schema.SessionCookieConfiguration{
|
||||||
config.Session.Name = "authelia_session"
|
{
|
||||||
config.Session.Domain = "example.com"
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session",
|
||||||
|
Domain: "example.com",
|
||||||
|
RememberMe: schema.DefaultSessionConfiguration.RememberMe,
|
||||||
|
Expiration: schema.DefaultSessionConfiguration.Expiration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: "authelia_session",
|
||||||
|
Domain: "example2.com",
|
||||||
|
RememberMe: schema.DefaultSessionConfiguration.RememberMe,
|
||||||
|
Expiration: schema.DefaultSessionConfiguration.Expiration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
config.AccessControl.DefaultPolicy = "deny"
|
config.AccessControl.DefaultPolicy = "deny"
|
||||||
config.AccessControl.Rules = []schema.ACLRule{{
|
config.AccessControl.Rules = []schema.ACLRule{{
|
||||||
Domains: []string{"bypass.example.com"},
|
Domains: []string{"bypass.example.com"},
|
||||||
|
@ -114,6 +130,9 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
||||||
// Set a cookie to identify this client throughout the test.
|
// Set a cookie to identify this client throughout the test.
|
||||||
// request.Request.Header.SetCookie("authelia_session", "client_cookie").
|
// request.Request.Header.SetCookie("authelia_session", "client_cookie").
|
||||||
|
|
||||||
|
// Set X-Forwarded-Host for compatibility with multi-root-domain implementation.
|
||||||
|
request.Request.Header.Set("X-Forwarded-Host", "example.com")
|
||||||
|
|
||||||
ctx := middlewares.NewAutheliaCtx(request, config, providers)
|
ctx := middlewares.NewAutheliaCtx(request, config, providers)
|
||||||
mockAuthelia.Ctx = ctx
|
mockAuthelia.Ctx = ctx
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,7 @@ func (d *WebauthnDevice) UpdateSignInInfo(config *webauthn.Config, now time.Time
|
||||||
|
|
||||||
switch d.AttestationType {
|
switch d.AttestationType {
|
||||||
case attestationTypeFIDOU2F:
|
case attestationTypeFIDOU2F:
|
||||||
d.RPID = config.RPOrigin
|
d.RPID = config.RPOrigins[0]
|
||||||
default:
|
default:
|
||||||
d.RPID = config.RPID
|
d.RPID = config.RPID
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,8 +139,6 @@ func (n *SMTPNotifier) Send(ctx context.Context, recipient mail.Address, subject
|
||||||
|
|
||||||
var client *gomail.Client
|
var client *gomail.Client
|
||||||
|
|
||||||
n.log.Debugf("creating client with %d options: %+v", len(n.opts), n.opts)
|
|
||||||
|
|
||||||
if client, err = gomail.NewClient(n.config.Host, n.opts...); err != nil {
|
if client, err = gomail.NewClient(n.config.Host, n.opts...); err != nil {
|
||||||
return fmt.Errorf("notifier: smtp: failed to establish client: %w", err)
|
return fmt.Errorf("notifier: smtp: failed to establish client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,27 @@
|
||||||
package ntp
|
package ntp
|
||||||
|
|
||||||
const (
|
|
||||||
ntpClientModeValue uint8 = 3 // 00000011.
|
|
||||||
ntpLeapEnabledValue uint8 = 64 // 01000000.
|
|
||||||
ntpVersion3Value uint8 = 24 // 00011000.
|
|
||||||
ntpVersion4Value uint8 = 40 // 00101000.
|
|
||||||
)
|
|
||||||
|
|
||||||
const ntpEpochOffset = 2208988800
|
const ntpEpochOffset = 2208988800
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ntpV3 ntpVersion = iota
|
ntpV3 ntpVersion = iota
|
||||||
ntpV4
|
ntpV4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maskMode = 0xf8
|
||||||
|
maskVersion = 0xc7
|
||||||
|
maskLeap = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
modeClient = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
version3 = 3
|
||||||
|
version4 = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
leapUnknown = 3
|
||||||
|
)
|
||||||
|
|
|
@ -40,7 +40,7 @@ func (p *Provider) StartupCheck() (err error) {
|
||||||
version = ntpV3
|
version = ntpV3
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &ntpPacket{LeapVersionMode: ntpLeapVersionClientMode(false, version)}
|
req := &ntpPacket{LeapVersionMode: ntpLeapVersionClientMode(version)}
|
||||||
|
|
||||||
if err := binary.Write(conn, binary.BigEndian, req); err != nil {
|
if err := binary.Write(conn, binary.BigEndian, req); err != nil {
|
||||||
p.log.Warnf("Could not write to the NTP server socket to validate the system time is properly synchronized: %+v", err)
|
p.log.Warnf("Could not write to the NTP server socket to validate the system time is properly synchronized: %+v", err)
|
||||||
|
|
|
@ -3,20 +3,18 @@ package ntp
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// ntpLeapVersionClientMode does the mathematics to configure the leap/version/mode value of an NTP client packet.
|
// ntpLeapVersionClientMode does the mathematics to configure the leap/version/mode value of an NTP client packet.
|
||||||
func ntpLeapVersionClientMode(leap bool, version ntpVersion) (lvm uint8) {
|
func ntpLeapVersionClientMode(version ntpVersion) (lvm uint8) {
|
||||||
lvm = ntpClientModeValue
|
lvm = (lvm & maskMode) | uint8(modeClient)
|
||||||
|
|
||||||
if leap {
|
|
||||||
lvm += ntpLeapEnabledValue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch version {
|
switch version {
|
||||||
case ntpV3:
|
case ntpV3:
|
||||||
lvm += ntpVersion3Value
|
lvm = (lvm & maskVersion) | uint8(version3)<<3
|
||||||
case ntpV4:
|
case ntpV4:
|
||||||
lvm += ntpVersion4Value
|
lvm = (lvm & maskVersion) | uint8(version4)<<3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lvm = (lvm & maskLeap) | uint8(leapUnknown)<<6
|
||||||
|
|
||||||
return lvm
|
return lvm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,13 +29,9 @@ func TestNtpPacketToTime(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLeapVersionClientMode(t *testing.T) {
|
func TestLeapVersionClientMode(t *testing.T) {
|
||||||
v3Noleap := uint8(27)
|
v3Noleap := uint8(0xdb)
|
||||||
v4Noleap := uint8(43)
|
v4Noleap := uint8(0xe3)
|
||||||
v3leap := uint8(91)
|
|
||||||
v4leap := uint8(107)
|
|
||||||
|
|
||||||
assert.Equal(t, v3Noleap, ntpLeapVersionClientMode(false, ntpV3))
|
assert.Equal(t, v3Noleap, ntpLeapVersionClientMode(ntpV3))
|
||||||
assert.Equal(t, v4Noleap, ntpLeapVersionClientMode(false, ntpV4))
|
assert.Equal(t, v4Noleap, ntpLeapVersionClientMode(ntpV4))
|
||||||
assert.Equal(t, v3leap, ntpLeapVersionClientMode(true, ntpV3))
|
|
||||||
assert.Equal(t, v4leap, ntpLeapVersionClientMode(true, ntpV4))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,11 @@ import (
|
||||||
"github.com/ory/fosite/token/jwt"
|
"github.com/ory/fosite/token/jwt"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/v4/internal/templates"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewConfig(config *schema.OpenIDConnectConfiguration) *Config {
|
func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.Provider) *Config {
|
||||||
c := &Config{
|
c := &Config{
|
||||||
GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)),
|
GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)),
|
||||||
SendDebugMessagesToClients: config.EnableClientDebugMessages,
|
SendDebugMessagesToClients: config.EnableClientDebugMessages,
|
||||||
|
@ -38,6 +39,7 @@ func NewConfig(config *schema.OpenIDConnectConfiguration) *Config {
|
||||||
EnforcePublicClients: config.EnforcePKCE != "never",
|
EnforcePublicClients: config.EnforcePKCE != "never",
|
||||||
AllowPlainChallengeMethod: config.EnablePKCEPlainChallenge,
|
AllowPlainChallengeMethod: config.EnablePKCEPlainChallenge,
|
||||||
},
|
},
|
||||||
|
Templates: templates,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Strategy.Core = &HMACCoreStrategy{
|
c.Strategy.Core = &HMACCoreStrategy{
|
||||||
|
@ -85,6 +87,8 @@ type Config struct {
|
||||||
HTTPClient *retryablehttp.Client
|
HTTPClient *retryablehttp.Client
|
||||||
FormPostHTMLTemplate *template.Template
|
FormPostHTMLTemplate *template.Template
|
||||||
MessageCatalog i18n.MessageCatalog
|
MessageCatalog i18n.MessageCatalog
|
||||||
|
|
||||||
|
Templates *templates.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
type HashConfig struct {
|
type HashConfig struct {
|
||||||
|
@ -502,7 +506,11 @@ func (c *Config) GetMessageCatalog(ctx context.Context) (catalog i18n.MessageCat
|
||||||
|
|
||||||
// GetFormPostHTMLTemplate returns the form post HTML template.
|
// GetFormPostHTMLTemplate returns the form post HTML template.
|
||||||
func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) (tmpl *template.Template) {
|
func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) (tmpl *template.Template) {
|
||||||
return c.FormPostHTMLTemplate
|
if c.Templates == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Templates.GetOpenIDConnectAuthorizeResponseFormPostTemplate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTokenURL returns the token URL.
|
// GetTokenURL returns the token URL.
|
||||||
|
|
|
@ -9,10 +9,11 @@ import (
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/storage"
|
"github.com/authelia/authelia/v4/internal/storage"
|
||||||
|
"github.com/authelia/authelia/v4/internal/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewOpenIDConnectProvider new-ups a OpenIDConnectProvider.
|
// NewOpenIDConnectProvider new-ups a OpenIDConnectProvider.
|
||||||
func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store storage.Provider) (provider *OpenIDConnectProvider, err error) {
|
func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store storage.Provider, templates *templates.Provider) (provider *OpenIDConnectProvider, err error) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -20,7 +21,7 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s
|
||||||
provider = &OpenIDConnectProvider{
|
provider = &OpenIDConnectProvider{
|
||||||
JSONWriter: herodot.NewJSONWriter(nil),
|
JSONWriter: herodot.NewJSONWriter(nil),
|
||||||
Store: NewStore(config, store),
|
Store: NewStore(config, store),
|
||||||
Config: NewConfig(config),
|
Config: NewConfig(config, templates),
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.OAuth2Provider = fosite.NewOAuth2Provider(provider.Store, provider.Config)
|
provider.OAuth2Provider = fosite.NewOAuth2Provider(provider.Store, provider.Config)
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
var exampleIssuerPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAvcMVMB2vEbqI6PlSNJ4HmUyMxBDJ5iY7FS+zDDAHOZBg9S3S\nKcAn1CZcnyL0VvJ7wcdhR6oTnOwR94eKvzUyJZ+GL2hTMm27dubEYsNdhoCl6N3X\nyEEohNfoxiiCYraVauX8X3M9jFzbEz9+pacaDbHB2syaJ1qFmMNR+HSu2jPzOo7M\nlqKIOgUzA0741MaYNt47AEVg4XU5ORLdolbAkItmYg1QbyFndg9H5IvwKkYaXTGE\nlgDBcPUC0yVjAC15Mguquq+jZeQay+6PSbHTD8PQMOkLjyChI2xEhVNbdCXe676R\ncMW2R/gjrcK23zmtmTWRfdC1iZLSlHO+bJj9vQIDAQABAoIBAEZvkP/JJOCJwqPn\nV3IcbmmilmV4bdi1vByDFgyiDyx4wOSA24+PubjvfFW9XcCgRPuKjDtTj/AhWBHv\nB7stfa2lZuNV7/u562mZArA+IAr62Zp0LdIxDV8x3T8gbjVB3HhPYbv0RJZDKTYd\nzV6jhfIrVu9mHpoY6ZnodhapCPYIyk/d49KBIHZuAc25CUjMXgTeaVtf0c996036\nUxW6ef33wAOJAvW0RCvbXAJfmBeEq2qQlkjTIlpYx71fhZWexHifi8Ouv3Zonc+1\n/P2Adq5uzYVBT92f9RKHg9QxxNzVrLjSMaxyvUtWQCAQfW0tFIRdqBGsHYsQrFtI\nF4yzv8ECgYEA7ntpyN9HD9Z9lYQzPCR73sFCLM+ID99aVij0wHuxK97bkSyyvkLd\n7MyTaym3lg1UEqWNWBCLvFULZx7F0Ah6qCzD4ymm3Bj/ADpWWPgljBI0AFml+HHs\nhcATmXUrj5QbLyhiP2gmJjajp1o/rgATx6ED66seSynD6JOH8wUhhZUCgYEAy7OA\n06PF8GfseNsTqlDjNF0K7lOqd21S0prdwrsJLiVzUlfMM25MLE0XLDUutCnRheeh\nIlcuDoBsVTxz6rkvFGD74N+pgXlN4CicsBq5ofK060PbqCQhSII3fmHobrZ9Cr75\nHmBjAxHx998SKaAAGbBbcYGUAp521i1pH5CEPYkCgYEAkUd1Zf0+2RMdZhwm6hh/\nrW+l1I6IoMK70YkZsLipccRNld7Y9LbfYwYtODcts6di9AkOVfueZJiaXbONZfIE\nZrb+jkAteh9wGL9xIrnohbABJcV3Kiaco84jInUSmGDtPokncOENfHIEuEpuSJ2b\nbx1TuhmAVuGWivR0+ULC7RECgYEAgS0cDRpWc9Xzh9Cl7+PLsXEvdWNpPsL9OsEq\n0Ep7z9+/+f/jZtoTRCS/BTHUpDvAuwHglT5j3p5iFMt5VuiIiovWLwynGYwrbnNS\nqfrIrYKUaH1n1oDS+oBZYLQGCe9/7EifAjxtjYzbvSyg//SPG7tSwfBCREbpZXj2\nqSWkNsECgYA/mCDzCTlrrWPuiepo6kTmN+4TnFA+hJI6NccDVQ+jvbqEdoJ4SW4L\nzqfZSZRFJMNpSgIqkQNRPJqMP0jQ5KRtJrjMWBnYxktwKz9fDg2R2MxdFgMF2LH2\nHEMMhFHlv8NDjVOXh1KwRoltNGVWYsSrD9wKU9GhRCEfmNCGrvBcEg==\n-----END RSA PRIVATE KEY-----"
|
var exampleIssuerPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAvcMVMB2vEbqI6PlSNJ4HmUyMxBDJ5iY7FS+zDDAHOZBg9S3S\nKcAn1CZcnyL0VvJ7wcdhR6oTnOwR94eKvzUyJZ+GL2hTMm27dubEYsNdhoCl6N3X\nyEEohNfoxiiCYraVauX8X3M9jFzbEz9+pacaDbHB2syaJ1qFmMNR+HSu2jPzOo7M\nlqKIOgUzA0741MaYNt47AEVg4XU5ORLdolbAkItmYg1QbyFndg9H5IvwKkYaXTGE\nlgDBcPUC0yVjAC15Mguquq+jZeQay+6PSbHTD8PQMOkLjyChI2xEhVNbdCXe676R\ncMW2R/gjrcK23zmtmTWRfdC1iZLSlHO+bJj9vQIDAQABAoIBAEZvkP/JJOCJwqPn\nV3IcbmmilmV4bdi1vByDFgyiDyx4wOSA24+PubjvfFW9XcCgRPuKjDtTj/AhWBHv\nB7stfa2lZuNV7/u562mZArA+IAr62Zp0LdIxDV8x3T8gbjVB3HhPYbv0RJZDKTYd\nzV6jhfIrVu9mHpoY6ZnodhapCPYIyk/d49KBIHZuAc25CUjMXgTeaVtf0c996036\nUxW6ef33wAOJAvW0RCvbXAJfmBeEq2qQlkjTIlpYx71fhZWexHifi8Ouv3Zonc+1\n/P2Adq5uzYVBT92f9RKHg9QxxNzVrLjSMaxyvUtWQCAQfW0tFIRdqBGsHYsQrFtI\nF4yzv8ECgYEA7ntpyN9HD9Z9lYQzPCR73sFCLM+ID99aVij0wHuxK97bkSyyvkLd\n7MyTaym3lg1UEqWNWBCLvFULZx7F0Ah6qCzD4ymm3Bj/ADpWWPgljBI0AFml+HHs\nhcATmXUrj5QbLyhiP2gmJjajp1o/rgATx6ED66seSynD6JOH8wUhhZUCgYEAy7OA\n06PF8GfseNsTqlDjNF0K7lOqd21S0prdwrsJLiVzUlfMM25MLE0XLDUutCnRheeh\nIlcuDoBsVTxz6rkvFGD74N+pgXlN4CicsBq5ofK060PbqCQhSII3fmHobrZ9Cr75\nHmBjAxHx998SKaAAGbBbcYGUAp521i1pH5CEPYkCgYEAkUd1Zf0+2RMdZhwm6hh/\nrW+l1I6IoMK70YkZsLipccRNld7Y9LbfYwYtODcts6di9AkOVfueZJiaXbONZfIE\nZrb+jkAteh9wGL9xIrnohbABJcV3Kiaco84jInUSmGDtPokncOENfHIEuEpuSJ2b\nbx1TuhmAVuGWivR0+ULC7RECgYEAgS0cDRpWc9Xzh9Cl7+PLsXEvdWNpPsL9OsEq\n0Ep7z9+/+f/jZtoTRCS/BTHUpDvAuwHglT5j3p5iFMt5VuiIiovWLwynGYwrbnNS\nqfrIrYKUaH1n1oDS+oBZYLQGCe9/7EifAjxtjYzbvSyg//SPG7tSwfBCREbpZXj2\nqSWkNsECgYA/mCDzCTlrrWPuiepo6kTmN+4TnFA+hJI6NccDVQ+jvbqEdoJ4SW4L\nzqfZSZRFJMNpSgIqkQNRPJqMP0jQ5KRtJrjMWBnYxktwKz9fDg2R2MxdFgMF2LH2\nHEMMhFHlv8NDjVOXh1KwRoltNGVWYsSrD9wKU9GhRCEfmNCGrvBcEg==\n-----END RSA PRIVATE KEY-----"
|
||||||
|
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(nil, nil)
|
provider, err := NewOpenIDConnectProvider(nil, nil, nil)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, provider)
|
assert.Nil(t, provider)
|
||||||
|
@ -39,7 +39,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NotNil(t, provider)
|
assert.NotNil(t, provider)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -109,7 +109,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -236,27 +236,27 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
if providers.OpenIDConnect != nil {
|
if providers.OpenIDConnect != nil {
|
||||||
middlewareOIDC := middlewares.NewBridgeBuilder(config, providers).WithPreMiddlewares(
|
bridgeOIDC := middlewares.NewBridgeBuilder(config, providers).WithPreMiddlewares(
|
||||||
middlewares.SecurityHeaders, middlewares.SecurityHeadersCSPNone, middlewares.SecurityHeadersNoStore,
|
middlewares.SecurityHeaders, middlewares.SecurityHeadersCSPNoneOpenIDConnect, middlewares.SecurityHeadersNoStore,
|
||||||
).Build()
|
).Build()
|
||||||
|
|
||||||
r.GET("/api/oidc/consent", middlewareOIDC(handlers.OpenIDConnectConsentGET))
|
r.GET("/api/oidc/consent", bridgeOIDC(handlers.OpenIDConnectConsentGET))
|
||||||
r.POST("/api/oidc/consent", middlewareOIDC(handlers.OpenIDConnectConsentPOST))
|
r.POST("/api/oidc/consent", bridgeOIDC(handlers.OpenIDConnectConsentPOST))
|
||||||
|
|
||||||
allowedOrigins := utils.StringSliceFromURLs(config.IdentityProviders.OIDC.CORS.AllowedOrigins)
|
allowedOrigins := utils.StringSliceFromURLs(config.IdentityProviders.OIDC.CORS.AllowedOrigins)
|
||||||
|
|
||||||
r.OPTIONS(oidc.EndpointPathWellKnownOpenIDConfiguration, policyCORSPublicGET.HandleOPTIONS)
|
r.OPTIONS(oidc.EndpointPathWellKnownOpenIDConfiguration, policyCORSPublicGET.HandleOPTIONS)
|
||||||
r.GET(oidc.EndpointPathWellKnownOpenIDConfiguration, policyCORSPublicGET.Middleware(middlewareOIDC(handlers.OpenIDConnectConfigurationWellKnownGET)))
|
r.GET(oidc.EndpointPathWellKnownOpenIDConfiguration, policyCORSPublicGET.Middleware(bridgeOIDC(handlers.OpenIDConnectConfigurationWellKnownGET)))
|
||||||
|
|
||||||
r.OPTIONS(oidc.EndpointPathWellKnownOAuthAuthorizationServer, policyCORSPublicGET.HandleOPTIONS)
|
r.OPTIONS(oidc.EndpointPathWellKnownOAuthAuthorizationServer, policyCORSPublicGET.HandleOPTIONS)
|
||||||
r.GET(oidc.EndpointPathWellKnownOAuthAuthorizationServer, policyCORSPublicGET.Middleware(middlewareOIDC(handlers.OAuthAuthorizationServerWellKnownGET)))
|
r.GET(oidc.EndpointPathWellKnownOAuthAuthorizationServer, policyCORSPublicGET.Middleware(bridgeOIDC(handlers.OAuthAuthorizationServerWellKnownGET)))
|
||||||
|
|
||||||
r.OPTIONS(oidc.EndpointPathJWKs, policyCORSPublicGET.HandleOPTIONS)
|
r.OPTIONS(oidc.EndpointPathJWKs, policyCORSPublicGET.HandleOPTIONS)
|
||||||
r.GET(oidc.EndpointPathJWKs, policyCORSPublicGET.Middleware(middlewareAPI(handlers.JSONWebKeySetGET)))
|
r.GET(oidc.EndpointPathJWKs, policyCORSPublicGET.Middleware(middlewareAPI(handlers.JSONWebKeySetGET)))
|
||||||
|
|
||||||
// TODO (james-d-elliott): Remove in GA. This is a legacy implementation of the above endpoint.
|
// TODO (james-d-elliott): Remove in GA. This is a legacy implementation of the above endpoint.
|
||||||
r.OPTIONS("/api/oidc/jwks", policyCORSPublicGET.HandleOPTIONS)
|
r.OPTIONS("/api/oidc/jwks", policyCORSPublicGET.HandleOPTIONS)
|
||||||
r.GET("/api/oidc/jwks", policyCORSPublicGET.Middleware(middlewareOIDC(handlers.JSONWebKeySetGET)))
|
r.GET("/api/oidc/jwks", policyCORSPublicGET.Middleware(bridgeOIDC(handlers.JSONWebKeySetGET)))
|
||||||
|
|
||||||
policyCORSAuthorization := middlewares.NewCORSPolicyBuilder().
|
policyCORSAuthorization := middlewares.NewCORSPolicyBuilder().
|
||||||
WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodGet, fasthttp.MethodPost).
|
WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodGet, fasthttp.MethodPost).
|
||||||
|
@ -265,13 +265,13 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
r.OPTIONS(oidc.EndpointPathAuthorization, policyCORSAuthorization.HandleOnlyOPTIONS)
|
r.OPTIONS(oidc.EndpointPathAuthorization, policyCORSAuthorization.HandleOnlyOPTIONS)
|
||||||
r.GET(oidc.EndpointPathAuthorization, policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
r.GET(oidc.EndpointPathAuthorization, policyCORSAuthorization.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
||||||
r.POST(oidc.EndpointPathAuthorization, policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
r.POST(oidc.EndpointPathAuthorization, policyCORSAuthorization.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
||||||
|
|
||||||
// TODO (james-d-elliott): Remove in GA. This is a legacy endpoint.
|
// TODO (james-d-elliott): Remove in GA. This is a legacy endpoint.
|
||||||
r.OPTIONS("/api/oidc/authorize", policyCORSAuthorization.HandleOnlyOPTIONS)
|
r.OPTIONS("/api/oidc/authorize", policyCORSAuthorization.HandleOnlyOPTIONS)
|
||||||
r.GET("/api/oidc/authorize", policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
r.GET("/api/oidc/authorize", policyCORSAuthorization.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
||||||
r.POST("/api/oidc/authorize", policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
r.POST("/api/oidc/authorize", policyCORSAuthorization.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
||||||
|
|
||||||
policyCORSToken := middlewares.NewCORSPolicyBuilder().
|
policyCORSToken := middlewares.NewCORSPolicyBuilder().
|
||||||
WithAllowCredentials(true).
|
WithAllowCredentials(true).
|
||||||
|
@ -281,7 +281,7 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
r.OPTIONS(oidc.EndpointPathToken, policyCORSToken.HandleOPTIONS)
|
r.OPTIONS(oidc.EndpointPathToken, policyCORSToken.HandleOPTIONS)
|
||||||
r.POST(oidc.EndpointPathToken, policyCORSToken.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectTokenPOST))))
|
r.POST(oidc.EndpointPathToken, policyCORSToken.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectTokenPOST))))
|
||||||
|
|
||||||
policyCORSUserinfo := middlewares.NewCORSPolicyBuilder().
|
policyCORSUserinfo := middlewares.NewCORSPolicyBuilder().
|
||||||
WithAllowCredentials(true).
|
WithAllowCredentials(true).
|
||||||
|
@ -291,8 +291,8 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
r.OPTIONS(oidc.EndpointPathUserinfo, policyCORSUserinfo.HandleOPTIONS)
|
r.OPTIONS(oidc.EndpointPathUserinfo, policyCORSUserinfo.HandleOPTIONS)
|
||||||
r.GET(oidc.EndpointPathUserinfo, policyCORSUserinfo.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
|
r.GET(oidc.EndpointPathUserinfo, policyCORSUserinfo.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
|
||||||
r.POST(oidc.EndpointPathUserinfo, policyCORSUserinfo.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
|
r.POST(oidc.EndpointPathUserinfo, policyCORSUserinfo.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
|
||||||
|
|
||||||
policyCORSIntrospection := middlewares.NewCORSPolicyBuilder().
|
policyCORSIntrospection := middlewares.NewCORSPolicyBuilder().
|
||||||
WithAllowCredentials(true).
|
WithAllowCredentials(true).
|
||||||
|
@ -302,11 +302,11 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
r.OPTIONS(oidc.EndpointPathIntrospection, policyCORSIntrospection.HandleOPTIONS)
|
r.OPTIONS(oidc.EndpointPathIntrospection, policyCORSIntrospection.HandleOPTIONS)
|
||||||
r.POST(oidc.EndpointPathIntrospection, policyCORSIntrospection.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthIntrospectionPOST))))
|
r.POST(oidc.EndpointPathIntrospection, policyCORSIntrospection.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthIntrospectionPOST))))
|
||||||
|
|
||||||
// TODO (james-d-elliott): Remove in GA. This is a legacy implementation of the above endpoint.
|
// TODO (james-d-elliott): Remove in GA. This is a legacy implementation of the above endpoint.
|
||||||
r.OPTIONS("/api/oidc/introspect", policyCORSIntrospection.HandleOPTIONS)
|
r.OPTIONS("/api/oidc/introspect", policyCORSIntrospection.HandleOPTIONS)
|
||||||
r.POST("/api/oidc/introspect", policyCORSIntrospection.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthIntrospectionPOST))))
|
r.POST("/api/oidc/introspect", policyCORSIntrospection.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthIntrospectionPOST))))
|
||||||
|
|
||||||
policyCORSRevocation := middlewares.NewCORSPolicyBuilder().
|
policyCORSRevocation := middlewares.NewCORSPolicyBuilder().
|
||||||
WithAllowCredentials(true).
|
WithAllowCredentials(true).
|
||||||
|
@ -316,11 +316,11 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
r.OPTIONS(oidc.EndpointPathRevocation, policyCORSRevocation.HandleOPTIONS)
|
r.OPTIONS(oidc.EndpointPathRevocation, policyCORSRevocation.HandleOPTIONS)
|
||||||
r.POST(oidc.EndpointPathRevocation, policyCORSRevocation.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthRevocationPOST))))
|
r.POST(oidc.EndpointPathRevocation, policyCORSRevocation.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthRevocationPOST))))
|
||||||
|
|
||||||
// TODO (james-d-elliott): Remove in GA. This is a legacy implementation of the above endpoint.
|
// TODO (james-d-elliott): Remove in GA. This is a legacy implementation of the above endpoint.
|
||||||
r.OPTIONS("/api/oidc/revoke", policyCORSRevocation.HandleOPTIONS)
|
r.OPTIONS("/api/oidc/revoke", policyCORSRevocation.HandleOPTIONS)
|
||||||
r.POST("/api/oidc/revoke", policyCORSRevocation.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthRevocationPOST))))
|
r.POST("/api/oidc/revoke", policyCORSRevocation.Middleware(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthRevocationPOST))))
|
||||||
}
|
}
|
||||||
|
|
||||||
r.HandleMethodNotAllowed = true
|
r.HandleMethodNotAllowed = true
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Base":"{{ .Base }}",
|
||||||
|
"DuoSelfEnrollment":"{{ .DuoSelfEnrollment }}",
|
||||||
|
"LogoOverride":"{{ .LogoOverride }}",
|
||||||
|
"RememberMe":"{{ .RememberMe }}",
|
||||||
|
"ResetPassword":"{{ .ResetPassword }}",
|
||||||
|
"ResetPasswordCustomURL":"{{ .ResetPasswordCustomURL }}",
|
||||||
|
"Theme":"{{ .Theme }}"
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/random"
|
"github.com/authelia/authelia/v4/internal/random"
|
||||||
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
"github.com/authelia/authelia/v4/internal/templates"
|
"github.com/authelia/authelia/v4/internal/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,7 +58,16 @@ func ServeTemplatedFile(t templates.Template, opts *TemplatedFileOptions) middle
|
||||||
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPDefault, nonce))
|
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(tmplCSPDefault, nonce))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = t.Execute(ctx.Response.BodyWriter(), opts.CommonData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce, logoOverride)); err != nil {
|
var (
|
||||||
|
rememberMe string
|
||||||
|
provider *session.Session
|
||||||
|
)
|
||||||
|
|
||||||
|
if provider, err = ctx.GetSessionProvider(); err == nil {
|
||||||
|
rememberMe = strconv.FormatBool(!provider.Config.DisableRememberMe)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = t.Execute(ctx.Response.BodyWriter(), opts.CommonData(ctx.BasePath(), ctx.RootURLSlash().String(), nonce, logoOverride, rememberMe)); err != nil {
|
||||||
ctx.RequestCtx.Error("an error occurred", 503)
|
ctx.RequestCtx.Error("an error occurred", 503)
|
||||||
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
ctx.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
||||||
|
|
||||||
|
@ -190,7 +200,7 @@ func NewTemplatedFileOptions(config *schema.Configuration) (opts *TemplatedFileO
|
||||||
opts = &TemplatedFileOptions{
|
opts = &TemplatedFileOptions{
|
||||||
AssetPath: config.Server.AssetPath,
|
AssetPath: config.Server.AssetPath,
|
||||||
DuoSelfEnrollment: strFalse,
|
DuoSelfEnrollment: strFalse,
|
||||||
RememberMe: strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled),
|
RememberMe: strconv.FormatBool(!config.Session.DisableRememberMe),
|
||||||
ResetPassword: strconv.FormatBool(!config.AuthenticationBackend.PasswordReset.Disable),
|
ResetPassword: strconv.FormatBool(!config.AuthenticationBackend.PasswordReset.Disable),
|
||||||
ResetPasswordCustomURL: config.AuthenticationBackend.PasswordReset.CustomURL.String(),
|
ResetPasswordCustomURL: config.AuthenticationBackend.PasswordReset.CustomURL.String(),
|
||||||
Theme: config.Theme,
|
Theme: config.Theme,
|
||||||
|
@ -227,7 +237,11 @@ type TemplatedFileOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonData returns a TemplatedFileCommonData with the dynamic options.
|
// CommonData returns a TemplatedFileCommonData with the dynamic options.
|
||||||
func (options *TemplatedFileOptions) CommonData(base, baseURL, nonce, logoOverride string) TemplatedFileCommonData {
|
func (options *TemplatedFileOptions) CommonData(base, baseURL, nonce, logoOverride, rememberMe string) TemplatedFileCommonData {
|
||||||
|
if rememberMe != "" {
|
||||||
|
return options.commonDataWithRememberMe(base, baseURL, nonce, logoOverride, rememberMe)
|
||||||
|
}
|
||||||
|
|
||||||
return TemplatedFileCommonData{
|
return TemplatedFileCommonData{
|
||||||
Base: base,
|
Base: base,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
|
@ -242,6 +256,22 @@ func (options *TemplatedFileOptions) CommonData(base, baseURL, nonce, logoOverri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommonDataWithRememberMe returns a TemplatedFileCommonData with the dynamic options.
|
||||||
|
func (options *TemplatedFileOptions) commonDataWithRememberMe(base, baseURL, nonce, logoOverride, rememberMe string) TemplatedFileCommonData {
|
||||||
|
return TemplatedFileCommonData{
|
||||||
|
Base: base,
|
||||||
|
BaseURL: baseURL,
|
||||||
|
CSPNonce: nonce,
|
||||||
|
LogoOverride: logoOverride,
|
||||||
|
DuoSelfEnrollment: options.DuoSelfEnrollment,
|
||||||
|
RememberMe: rememberMe,
|
||||||
|
ResetPassword: options.ResetPassword,
|
||||||
|
ResetPasswordCustomURL: options.ResetPasswordCustomURL,
|
||||||
|
Session: options.Session,
|
||||||
|
Theme: options.Theme,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// OpenAPIData returns a TemplatedFileOpenAPIData with the dynamic options.
|
// OpenAPIData returns a TemplatedFileOpenAPIData with the dynamic options.
|
||||||
func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, nonce string) TemplatedFileOpenAPIData {
|
func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, nonce string) TemplatedFileOpenAPIData {
|
||||||
return TemplatedFileOpenAPIData{
|
return TemplatedFileOpenAPIData{
|
||||||
|
|
|
@ -9,6 +9,12 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Serializer is a function that can serialize session information.
|
||||||
|
type Serializer interface {
|
||||||
|
Encode(src session.Dict) (data []byte, err error)
|
||||||
|
Decode(dst *session.Dict, src []byte) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
// EncryptingSerializer a serializer encrypting the data with AES-GCM with 256-bit keys.
|
// EncryptingSerializer a serializer encrypting the data with AES-GCM with 256-bit keys.
|
||||||
type EncryptingSerializer struct {
|
type EncryptingSerializer struct {
|
||||||
key [32]byte
|
key [32]byte
|
||||||
|
@ -21,8 +27,8 @@ func NewEncryptingSerializer(secret string) *EncryptingSerializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode encode and encrypt session.
|
// Encode encode and encrypt session.
|
||||||
func (e *EncryptingSerializer) Encode(src session.Dict) ([]byte, error) {
|
func (e *EncryptingSerializer) Encode(src session.Dict) (data []byte, err error) {
|
||||||
if len(src.D) == 0 {
|
if len(src.KV) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,28 +37,30 @@ func (e *EncryptingSerializer) Encode(src session.Dict) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("unable to marshal session: %v", err)
|
return nil, fmt.Errorf("unable to marshal session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedDst, err := utils.Encrypt(dst, &e.key)
|
if data, err = utils.Encrypt(dst, &e.key); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to encrypt session: %v", err)
|
return nil, fmt.Errorf("unable to encrypt session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return encryptedDst, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode decrypt and decode session.
|
// Decode decrypt and decode session.
|
||||||
func (e *EncryptingSerializer) Decode(dst *session.Dict, src []byte) error {
|
func (e *EncryptingSerializer) Decode(dst *session.Dict, src []byte) (err error) {
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.Reset()
|
for k := range dst.KV {
|
||||||
|
delete(dst.KV, k)
|
||||||
|
}
|
||||||
|
|
||||||
decryptedSrc, err := utils.Decrypt(src, &e.key)
|
var data []byte
|
||||||
if err != nil {
|
|
||||||
|
if data, err = utils.Decrypt(src, &e.key); err != nil {
|
||||||
return fmt.Errorf("unable to decrypt session: %s", err)
|
return fmt.Errorf("unable to decrypt session: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = dst.UnmarshalMsg(decryptedSrc)
|
_, err = dst.UnmarshalMsg(data)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShouldEncryptAndDecrypt(t *testing.T) {
|
func TestShouldEncryptAndDecrypt(t *testing.T) {
|
||||||
payload := session.Dict{}
|
payload := session.Dict{KV: map[string]interface{}{"key": "value"}}
|
||||||
payload.Set("key", "value")
|
|
||||||
|
|
||||||
dst, err := payload.MarshalMsg(nil)
|
dst, err := payload.MarshalMsg(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -25,12 +24,11 @@ func TestShouldEncryptAndDecrypt(t *testing.T) {
|
||||||
err = serializer.Decode(&decodedPayload, encryptedDst)
|
err = serializer.Decode(&decodedPayload, encryptedDst)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "value", decodedPayload.Get("key"))
|
assert.Equal(t, "value", decodedPayload.KV["key"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotSupportUnencryptedSessionForBackwardCompatibility(t *testing.T) {
|
func TestShouldNotSupportUnencryptedSessionForBackwardCompatibility(t *testing.T) {
|
||||||
payload := session.Dict{}
|
payload := session.Dict{KV: map[string]interface{}{"key": "value"}}
|
||||||
payload.Set("key", "value")
|
|
||||||
|
|
||||||
dst, err := payload.MarshalMsg(nil)
|
dst, err := payload.MarshalMsg(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -1,208 +0,0 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/fasthttp/session/v2 (interfaces: Storer)
|
|
||||||
|
|
||||||
// Package mock_session is a generated GoMock package.
|
|
||||||
package mock_session
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fasthttp/session/v2"
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockStorer is a mock of Storer interface
|
|
||||||
type MockStorer struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockStorerMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockStorerMockRecorder is the mock recorder for MockStorer
|
|
||||||
type MockStorerMockRecorder struct {
|
|
||||||
mock *MockStorer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockStorer creates a new mock instance
|
|
||||||
func NewMockStorer(ctrl *gomock.Controller) *MockStorer {
|
|
||||||
mock := &MockStorer{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockStorerMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
|
||||||
func (m *MockStorer) EXPECT() *MockStorerMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete mocks base method
|
|
||||||
func (m *MockStorer) Delete(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Delete", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete indicates an expected call of Delete
|
|
||||||
func (mr *MockStorerMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockStorer)(nil).Delete), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteBytes mocks base method
|
|
||||||
func (m *MockStorer) DeleteBytes(arg0 []byte) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "DeleteBytes", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteBytes indicates an expected call of DeleteBytes
|
|
||||||
func (mr *MockStorerMockRecorder) DeleteBytes(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBytes", reflect.TypeOf((*MockStorer)(nil).DeleteBytes), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush mocks base method
|
|
||||||
func (m *MockStorer) Flush() {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Flush")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush indicates an expected call of Flush
|
|
||||||
func (mr *MockStorerMockRecorder) Flush() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Flush", reflect.TypeOf((*MockStorer)(nil).Flush))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get mocks base method
|
|
||||||
func (m *MockStorer) Get(arg0 string) interface{} {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Get", arg0)
|
|
||||||
ret0, _ := ret[0].(interface{})
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get indicates an expected call of Get
|
|
||||||
func (mr *MockStorerMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockStorer)(nil).Get), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll mocks base method
|
|
||||||
func (m *MockStorer) GetAll() session.Dict {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetAll")
|
|
||||||
ret0, _ := ret[0].(session.Dict)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll indicates an expected call of GetAll
|
|
||||||
func (mr *MockStorerMockRecorder) GetAll() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockStorer)(nil).GetAll))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBytes mocks base method
|
|
||||||
func (m *MockStorer) GetBytes(arg0 []byte) interface{} {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetBytes", arg0)
|
|
||||||
ret0, _ := ret[0].(interface{})
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBytes indicates an expected call of GetBytes
|
|
||||||
func (mr *MockStorerMockRecorder) GetBytes(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBytes", reflect.TypeOf((*MockStorer)(nil).GetBytes), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetExpiration mocks base method
|
|
||||||
func (m *MockStorer) GetExpiration() time.Duration {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetExpiration")
|
|
||||||
ret0, _ := ret[0].(time.Duration)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetExpiration indicates an expected call of GetExpiration
|
|
||||||
func (mr *MockStorerMockRecorder) GetExpiration() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExpiration", reflect.TypeOf((*MockStorer)(nil).GetExpiration))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSessionID mocks base method
|
|
||||||
func (m *MockStorer) GetSessionID() []byte {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetSessionID")
|
|
||||||
ret0, _ := ret[0].([]byte)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSessionID indicates an expected call of GetSessionID
|
|
||||||
func (mr *MockStorerMockRecorder) GetSessionID() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSessionID", reflect.TypeOf((*MockStorer)(nil).GetSessionID))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasExpirationChanged mocks base method
|
|
||||||
func (m *MockStorer) HasExpirationChanged() bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HasExpirationChanged")
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasExpirationChanged indicates an expected call of HasExpirationChanged
|
|
||||||
func (mr *MockStorerMockRecorder) HasExpirationChanged() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasExpirationChanged", reflect.TypeOf((*MockStorer)(nil).HasExpirationChanged))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save mocks base method
|
|
||||||
func (m *MockStorer) Save() error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Save")
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save indicates an expected call of Save
|
|
||||||
func (mr *MockStorerMockRecorder) Save() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockStorer)(nil).Save))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set mocks base method
|
|
||||||
func (m *MockStorer) Set(arg0 string, arg1 interface{}) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Set", arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set indicates an expected call of Set
|
|
||||||
func (mr *MockStorerMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockStorer)(nil).Set), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBytes mocks base method
|
|
||||||
func (m *MockStorer) SetBytes(arg0 []byte, arg1 interface{}) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "SetBytes", arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBytes indicates an expected call of SetBytes
|
|
||||||
func (mr *MockStorerMockRecorder) SetBytes(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBytes", reflect.TypeOf((*MockStorer)(nil).SetBytes), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetExpiration mocks base method
|
|
||||||
func (m *MockStorer) SetExpiration(arg0 time.Duration) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetExpiration", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetExpiration indicates an expected call of SetExpiration
|
|
||||||
func (mr *MockStorerMockRecorder) SetExpiration(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetExpiration", reflect.TypeOf((*MockStorer)(nil).SetExpiration), arg0)
|
|
||||||
}
|
|
|
@ -2,158 +2,61 @@ package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
fasthttpsession "github.com/fasthttp/session/v2"
|
"github.com/fasthttp/session/v2"
|
||||||
"github.com/fasthttp/session/v2/providers/memory"
|
|
||||||
"github.com/fasthttp/session/v2/providers/redis"
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/logging"
|
"github.com/authelia/authelia/v4/internal/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider a session provider.
|
// Provider contains a list of domain sessions.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
sessionHolder *fasthttpsession.Session
|
sessions map[string]*Session
|
||||||
RememberMe time.Duration
|
|
||||||
Inactivity time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProvider instantiate a session provider given a configuration.
|
// NewProvider instantiate a session provider given a configuration.
|
||||||
func NewProvider(config schema.SessionConfiguration, certPool *x509.CertPool) *Provider {
|
func NewProvider(config schema.SessionConfiguration, certPool *x509.CertPool) *Provider {
|
||||||
c := NewProviderConfig(config, certPool)
|
log := logging.Logger()
|
||||||
|
|
||||||
provider := new(Provider)
|
name, p, s, err := NewSessionProvider(config, certPool)
|
||||||
provider.sessionHolder = fasthttpsession.New(c.config)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
logger := logging.Logger()
|
provider := &Provider{
|
||||||
|
sessions: map[string]*Session{},
|
||||||
provider.Inactivity, provider.RememberMe = config.Inactivity, config.RememberMeDuration
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
providerImpl fasthttpsession.Provider
|
holder *session.Session
|
||||||
err error
|
|
||||||
)
|
)
|
||||||
|
|
||||||
switch {
|
for _, dconfig := range config.Cookies {
|
||||||
case c.redisConfig != nil:
|
if _, holder, err = NewProviderConfigAndSession(dconfig, name, s, p); err != nil {
|
||||||
providerImpl, err = redis.New(*c.redisConfig)
|
log.Fatal(err)
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err)
|
|
||||||
}
|
|
||||||
case c.redisSentinelConfig != nil:
|
|
||||||
providerImpl, err = redis.NewFailoverCluster(*c.redisSentinelConfig)
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
providerImpl, err = memory.New(memory.Config{})
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = provider.sessionHolder.SetProvider(providerImpl)
|
provider.sessions[dconfig.Domain] = &Session{
|
||||||
if err != nil {
|
Config: dconfig,
|
||||||
logger.Fatal(err)
|
sessionHolder: holder,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSession return the user session from a request.
|
// Get returns session information for specified domain.
|
||||||
func (p *Provider) GetSession(ctx *fasthttp.RequestCtx) (UserSession, error) {
|
func (p *Provider) Get(domain string) (*Session, error) {
|
||||||
store, err := p.sessionHolder.Get(ctx)
|
if domain == "" {
|
||||||
|
return nil, fmt.Errorf("can not get session from an undefined domain")
|
||||||
if err != nil {
|
|
||||||
return NewDefaultUserSession(), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userSessionJSON, ok := store.Get(userSessionStorerKey).([]byte)
|
s, found := p.sessions[domain]
|
||||||
|
|
||||||
// If userSession is not yet defined we create the new session with default values
|
if !found {
|
||||||
// and save it in the store.
|
return nil, fmt.Errorf("no session found for domain '%s'", domain)
|
||||||
if !ok {
|
|
||||||
userSession := NewDefaultUserSession()
|
|
||||||
|
|
||||||
store.Set(userSessionStorerKey, userSession)
|
|
||||||
|
|
||||||
return userSession, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var userSession UserSession
|
return s, nil
|
||||||
err = json.Unmarshal(userSessionJSON, &userSession)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return NewDefaultUserSession(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
return userSession, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveSession save the user session.
|
|
||||||
func (p *Provider) SaveSession(ctx *fasthttp.RequestCtx, userSession UserSession) error {
|
|
||||||
store, err := p.sessionHolder.Get(ctx)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userSessionJSON, err := json.Marshal(userSession)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
store.Set(userSessionStorerKey, userSessionJSON)
|
|
||||||
|
|
||||||
err = p.sessionHolder.Save(ctx, store)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegenerateSession regenerate a session ID.
|
|
||||||
func (p *Provider) RegenerateSession(ctx *fasthttp.RequestCtx) error {
|
|
||||||
err := p.sessionHolder.Regenerate(ctx)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DestroySession destroy a session ID and delete the cookie.
|
|
||||||
func (p *Provider) DestroySession(ctx *fasthttp.RequestCtx) error {
|
|
||||||
return p.sessionHolder.Destroy(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateExpiration update the expiration of the cookie and session.
|
|
||||||
func (p *Provider) UpdateExpiration(ctx *fasthttp.RequestCtx, expiration time.Duration) error {
|
|
||||||
store, err := p.sessionHolder.Get(ctx)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.SetExpiration(expiration)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.sessionHolder.Save(ctx, store)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetExpiration get the expiration of the current session.
|
|
||||||
func (p *Provider) GetExpiration(ctx *fasthttp.RequestCtx) (time.Duration, error) {
|
|
||||||
store, err := p.sessionHolder.Get(ctx)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return time.Duration(0), err
|
|
||||||
}
|
|
||||||
|
|
||||||
return store.GetExpiration(), nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fasthttp/session/v2"
|
"github.com/fasthttp/session/v2"
|
||||||
|
"github.com/fasthttp/session/v2/providers/memory"
|
||||||
"github.com/fasthttp/session/v2/providers/redis"
|
"github.com/fasthttp/session/v2/providers/redis"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
@ -18,7 +19,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewProviderConfig creates a configuration for creating the session provider.
|
// NewProviderConfig creates a configuration for creating the session provider.
|
||||||
func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPool) ProviderConfig {
|
func NewProviderConfig(config schema.SessionCookieConfiguration, providerName string, serializer Serializer) ProviderConfig {
|
||||||
c := session.NewDefaultConfig()
|
c := session.NewDefaultConfig()
|
||||||
|
|
||||||
c.SessionIDGeneratorFunc = func() []byte {
|
c.SessionIDGeneratorFunc = func() []byte {
|
||||||
|
@ -61,16 +62,42 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var redisConfig *redis.Config
|
if serializer != nil {
|
||||||
|
c.EncodeFunc = serializer.Encode
|
||||||
|
c.DecodeFunc = serializer.Decode
|
||||||
|
}
|
||||||
|
|
||||||
var redisSentinelConfig *redis.FailoverConfig
|
return ProviderConfig{
|
||||||
|
c,
|
||||||
|
providerName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var providerName string
|
func NewProviderSession(pconfig ProviderConfig, provider session.Provider) (p *session.Session, err error) {
|
||||||
|
p = session.New(pconfig.config)
|
||||||
|
|
||||||
|
if err = p.SetProvider(provider); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProviderConfigAndSession(config schema.SessionCookieConfiguration, providerName string, serializer Serializer, provider session.Provider) (c ProviderConfig, p *session.Session, err error) {
|
||||||
|
c = NewProviderConfig(config, providerName, serializer)
|
||||||
|
|
||||||
|
if p, err = NewProviderSession(c, provider); err != nil {
|
||||||
|
return c, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSessionProvider(config schema.SessionConfiguration, certPool *x509.CertPool) (name string, provider session.Provider, serializer Serializer, err error) {
|
||||||
// If redis configuration is provided, then use the redis provider.
|
// If redis configuration is provided, then use the redis provider.
|
||||||
switch {
|
switch {
|
||||||
case config.Redis != nil:
|
case config.Redis != nil:
|
||||||
serializer := NewEncryptingSerializer(config.Secret)
|
serializer = NewEncryptingSerializer(config.Secret)
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
|
|
||||||
|
@ -92,8 +119,9 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
providerName = "redis-sentinel"
|
name = "redis-sentinel"
|
||||||
redisSentinelConfig = &redis.FailoverConfig{
|
|
||||||
|
provider, err = redis.NewFailoverCluster(redis.FailoverConfig{
|
||||||
Logger: logging.LoggerCtxPrintf(logrus.TraceLevel),
|
Logger: logging.LoggerCtxPrintf(logrus.TraceLevel),
|
||||||
MasterName: config.Redis.HighAvailability.SentinelName,
|
MasterName: config.Redis.HighAvailability.SentinelName,
|
||||||
SentinelAddrs: addrs,
|
SentinelAddrs: addrs,
|
||||||
|
@ -109,9 +137,9 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
|
||||||
IdleTimeout: 300,
|
IdleTimeout: 300,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
KeyPrefix: "authelia-session",
|
KeyPrefix: "authelia-session",
|
||||||
}
|
})
|
||||||
} else {
|
} else {
|
||||||
providerName = "redis"
|
name = "redis"
|
||||||
network := "tcp"
|
network := "tcp"
|
||||||
|
|
||||||
var addr string
|
var addr string
|
||||||
|
@ -123,7 +151,7 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
|
||||||
addr = fmt.Sprintf("%s:%d", config.Redis.Host, config.Redis.Port)
|
addr = fmt.Sprintf("%s:%d", config.Redis.Host, config.Redis.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
redisConfig = &redis.Config{
|
provider, err = redis.New(redis.Config{
|
||||||
Logger: logging.LoggerCtxPrintf(logrus.TraceLevel),
|
Logger: logging.LoggerCtxPrintf(logrus.TraceLevel),
|
||||||
Network: network,
|
Network: network,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
|
@ -135,19 +163,12 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
|
||||||
IdleTimeout: 300,
|
IdleTimeout: 300,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
KeyPrefix: "authelia-session",
|
KeyPrefix: "authelia-session",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
c.EncodeFunc = serializer.Encode
|
|
||||||
c.DecodeFunc = serializer.Decode
|
|
||||||
default:
|
default:
|
||||||
providerName = "memory"
|
name = "memory"
|
||||||
|
provider, err = memory.New(memory.Config{})
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProviderConfig{
|
return name, provider, serializer, err
|
||||||
c,
|
|
||||||
redisConfig,
|
|
||||||
redisSentinelConfig,
|
|
||||||
providerName,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,288 +0,0 @@
|
||||||
package session
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/tls"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fasthttp/session/v2"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestShouldCreateInMemorySessionProvider(t *testing.T) {
|
|
||||||
// The redis configuration is not provided so we create a in-memory provider.
|
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Domain = testDomain
|
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
providerConfig := NewProviderConfig(configuration, nil)
|
|
||||||
|
|
||||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
|
||||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
|
||||||
assert.Equal(t, true, providerConfig.config.Secure)
|
|
||||||
assert.Equal(t, time.Duration(40)*time.Second, providerConfig.config.Expiration)
|
|
||||||
assert.True(t, providerConfig.config.IsSecureFunc(nil))
|
|
||||||
assert.Equal(t, "memory", providerConfig.providerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldCreateRedisSessionProviderTLS(t *testing.T) {
|
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Domain = testDomain
|
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
|
||||||
Host: "redis.example.com",
|
|
||||||
Port: 6379,
|
|
||||||
Password: "pass",
|
|
||||||
TLS: &schema.TLSConfig{
|
|
||||||
ServerName: "redis.fqdn.example.com",
|
|
||||||
MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
providerConfig := NewProviderConfig(configuration, nil)
|
|
||||||
|
|
||||||
assert.Nil(t, providerConfig.redisSentinelConfig)
|
|
||||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
|
||||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
|
||||||
assert.Equal(t, true, providerConfig.config.Secure)
|
|
||||||
assert.Equal(t, time.Duration(40)*time.Second, providerConfig.config.Expiration)
|
|
||||||
assert.True(t, providerConfig.config.IsSecureFunc(nil))
|
|
||||||
|
|
||||||
assert.Equal(t, "redis", providerConfig.providerName)
|
|
||||||
|
|
||||||
pConfig := providerConfig.redisConfig
|
|
||||||
assert.Equal(t, "redis.example.com:6379", pConfig.Addr)
|
|
||||||
assert.Equal(t, "pass", pConfig.Password)
|
|
||||||
// DbNumber is the fasthttp/session property for the Redis DB Index.
|
|
||||||
assert.Equal(t, 0, pConfig.DB)
|
|
||||||
assert.Equal(t, 0, pConfig.PoolSize)
|
|
||||||
assert.Equal(t, 0, pConfig.MinIdleConns)
|
|
||||||
|
|
||||||
require.NotNil(t, pConfig.TLSConfig)
|
|
||||||
require.Equal(t, uint16(tls.VersionTLS13), pConfig.TLSConfig.MinVersion)
|
|
||||||
require.Equal(t, "redis.fqdn.example.com", pConfig.TLSConfig.ServerName)
|
|
||||||
require.False(t, pConfig.TLSConfig.InsecureSkipVerify)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldCreateRedisSessionProvider(t *testing.T) {
|
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Domain = testDomain
|
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
|
||||||
Host: "redis.example.com",
|
|
||||||
Port: 6379,
|
|
||||||
Password: "pass",
|
|
||||||
}
|
|
||||||
providerConfig := NewProviderConfig(configuration, nil)
|
|
||||||
|
|
||||||
assert.Nil(t, providerConfig.redisSentinelConfig)
|
|
||||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
|
||||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
|
||||||
assert.Equal(t, true, providerConfig.config.Secure)
|
|
||||||
assert.Equal(t, time.Duration(40)*time.Second, providerConfig.config.Expiration)
|
|
||||||
assert.True(t, providerConfig.config.IsSecureFunc(nil))
|
|
||||||
|
|
||||||
assert.Equal(t, "redis", providerConfig.providerName)
|
|
||||||
|
|
||||||
pConfig := providerConfig.redisConfig
|
|
||||||
assert.Equal(t, "redis.example.com:6379", pConfig.Addr)
|
|
||||||
assert.Equal(t, "pass", pConfig.Password)
|
|
||||||
// DbNumber is the fasthttp/session property for the Redis DB Index.
|
|
||||||
assert.Equal(t, 0, pConfig.DB)
|
|
||||||
assert.Equal(t, 0, pConfig.PoolSize)
|
|
||||||
assert.Equal(t, 0, pConfig.MinIdleConns)
|
|
||||||
|
|
||||||
assert.Nil(t, pConfig.TLSConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldCreateRedisSentinelSessionProviderWithoutDuplicateHosts(t *testing.T) {
|
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Domain = testDomain
|
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
|
||||||
Host: "REDIS.example.com",
|
|
||||||
Port: 26379,
|
|
||||||
Password: "pass",
|
|
||||||
MaximumActiveConnections: 8,
|
|
||||||
MinimumIdleConnections: 2,
|
|
||||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
|
||||||
SentinelName: "mysent",
|
|
||||||
SentinelPassword: "mypass",
|
|
||||||
Nodes: []schema.RedisNode{
|
|
||||||
{
|
|
||||||
Host: "redis2.example.com",
|
|
||||||
Port: 26379,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Host: "redis.example.com",
|
|
||||||
Port: 26379,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
providerConfig := NewProviderConfig(configuration, nil)
|
|
||||||
|
|
||||||
assert.Len(t, providerConfig.redisSentinelConfig.SentinelAddrs, 2)
|
|
||||||
assert.Equal(t, providerConfig.redisSentinelConfig.SentinelAddrs[0], "redis.example.com:26379")
|
|
||||||
assert.Equal(t, providerConfig.redisSentinelConfig.SentinelAddrs[1], "redis2.example.com:26379")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldCreateRedisSentinelSessionProvider(t *testing.T) {
|
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Domain = testDomain
|
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
|
||||||
Host: "redis.example.com",
|
|
||||||
Port: 26379,
|
|
||||||
Password: "pass",
|
|
||||||
MaximumActiveConnections: 8,
|
|
||||||
MinimumIdleConnections: 2,
|
|
||||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
|
||||||
SentinelName: "mysent",
|
|
||||||
SentinelPassword: "mypass",
|
|
||||||
Nodes: []schema.RedisNode{
|
|
||||||
{
|
|
||||||
Host: "redis2.example.com",
|
|
||||||
Port: 26379,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
providerConfig := NewProviderConfig(configuration, nil)
|
|
||||||
|
|
||||||
assert.Nil(t, providerConfig.redisConfig)
|
|
||||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
|
||||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
|
||||||
assert.Equal(t, true, providerConfig.config.Secure)
|
|
||||||
assert.Equal(t, time.Duration(40)*time.Second, providerConfig.config.Expiration)
|
|
||||||
assert.True(t, providerConfig.config.IsSecureFunc(nil))
|
|
||||||
|
|
||||||
assert.Equal(t, "redis-sentinel", providerConfig.providerName)
|
|
||||||
|
|
||||||
pConfig := providerConfig.redisSentinelConfig
|
|
||||||
assert.Equal(t, "redis.example.com:26379", pConfig.SentinelAddrs[0])
|
|
||||||
assert.Equal(t, "redis2.example.com:26379", pConfig.SentinelAddrs[1])
|
|
||||||
assert.Equal(t, "pass", pConfig.Password)
|
|
||||||
assert.Equal(t, "mysent", pConfig.MasterName)
|
|
||||||
assert.Equal(t, "mypass", pConfig.SentinelPassword)
|
|
||||||
assert.False(t, pConfig.RouteRandomly)
|
|
||||||
assert.False(t, pConfig.RouteByLatency)
|
|
||||||
assert.Equal(t, 8, pConfig.PoolSize)
|
|
||||||
assert.Equal(t, 2, pConfig.MinIdleConns)
|
|
||||||
|
|
||||||
// DbNumber is the fasthttp/session property for the Redis DB Index.
|
|
||||||
assert.Equal(t, 0, pConfig.DB)
|
|
||||||
assert.Nil(t, pConfig.TLSConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldSetCookieSameSite(t *testing.T) {
|
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Domain = testDomain
|
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
|
|
||||||
configValueExpectedValue := map[string]fasthttp.CookieSameSite{
|
|
||||||
"": fasthttp.CookieSameSiteLaxMode,
|
|
||||||
"lax": fasthttp.CookieSameSiteLaxMode,
|
|
||||||
"strict": fasthttp.CookieSameSiteStrictMode,
|
|
||||||
"none": fasthttp.CookieSameSiteNoneMode,
|
|
||||||
"invalid": fasthttp.CookieSameSiteLaxMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
for configValue, expectedValue := range configValueExpectedValue {
|
|
||||||
configuration.SameSite = configValue
|
|
||||||
providerConfig := NewProviderConfig(configuration, nil)
|
|
||||||
|
|
||||||
assert.Equal(t, expectedValue, providerConfig.config.CookieSameSite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldCreateRedisSessionProviderWithUnixSocket(t *testing.T) {
|
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Domain = testDomain
|
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
|
||||||
Host: "/var/run/redis/redis.sock",
|
|
||||||
Port: 0,
|
|
||||||
Password: "pass",
|
|
||||||
}
|
|
||||||
|
|
||||||
providerConfig := NewProviderConfig(configuration, nil)
|
|
||||||
|
|
||||||
assert.Nil(t, providerConfig.redisSentinelConfig)
|
|
||||||
|
|
||||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
|
||||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
|
||||||
assert.Equal(t, true, providerConfig.config.Secure)
|
|
||||||
assert.Equal(t, time.Duration(40)*time.Second, providerConfig.config.Expiration)
|
|
||||||
assert.True(t, providerConfig.config.IsSecureFunc(nil))
|
|
||||||
|
|
||||||
assert.Equal(t, "redis", providerConfig.providerName)
|
|
||||||
|
|
||||||
pConfig := providerConfig.redisConfig
|
|
||||||
assert.Equal(t, "/var/run/redis/redis.sock", pConfig.Addr)
|
|
||||||
assert.Equal(t, "pass", pConfig.Password)
|
|
||||||
// DbNumber is the fasthttp/session property for the Redis DB Index.
|
|
||||||
assert.Equal(t, 0, pConfig.DB)
|
|
||||||
assert.Nil(t, pConfig.TLSConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldSetDbNumber(t *testing.T) {
|
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Domain = testDomain
|
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
|
||||||
Host: "redis.example.com",
|
|
||||||
Port: 6379,
|
|
||||||
Password: "pass",
|
|
||||||
DatabaseIndex: 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
providerConfig := NewProviderConfig(configuration, nil)
|
|
||||||
|
|
||||||
assert.Nil(t, providerConfig.redisSentinelConfig)
|
|
||||||
|
|
||||||
assert.Equal(t, "redis", providerConfig.providerName)
|
|
||||||
pConfig := providerConfig.redisConfig
|
|
||||||
// DbNumber is the fasthttp/session property for the Redis DB Index.
|
|
||||||
assert.Equal(t, 5, pConfig.DB)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldUseEncryptingSerializerWithRedis(t *testing.T) {
|
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Secret = "abc"
|
|
||||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
|
||||||
Host: "redis.example.com",
|
|
||||||
Port: 6379,
|
|
||||||
Password: "pass",
|
|
||||||
DatabaseIndex: 5,
|
|
||||||
}
|
|
||||||
providerConfig := NewProviderConfig(configuration, nil)
|
|
||||||
|
|
||||||
payload := session.Dict{}
|
|
||||||
payload.Set("key", "value")
|
|
||||||
|
|
||||||
encoded, err := providerConfig.config.EncodeFunc(payload)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Now we try to decrypt what has been serialized.
|
|
||||||
key := sha256.Sum256([]byte("abc"))
|
|
||||||
decrypted, err := utils.Decrypt(encoded, &key)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
decoded := session.Dict{}
|
|
||||||
_, _ = decoded.UnmarshalMsg(decrypted)
|
|
||||||
assert.Equal(t, "value", decoded.Get("key"))
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
|
@ -14,16 +13,31 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/oidc"
|
"github.com/authelia/authelia/v4/internal/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func newTestSession() (*Session, error) {
|
||||||
|
config := schema.SessionConfiguration{}
|
||||||
|
config.Cookies = []schema.SessionCookieConfiguration{
|
||||||
|
{
|
||||||
|
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||||
|
Name: testName,
|
||||||
|
Domain: testDomain,
|
||||||
|
Expiration: testExpiration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := NewProvider(config, nil)
|
||||||
|
|
||||||
|
return provider.Get(testDomain)
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldInitializerSession(t *testing.T) {
|
func TestShouldInitializerSession(t *testing.T) {
|
||||||
ctx := &fasthttp.RequestCtx{}
|
ctx := &fasthttp.RequestCtx{}
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
configuration.Domain = testDomain
|
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
|
|
||||||
provider := NewProvider(configuration, nil)
|
provider, err := newTestSession()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err := provider.GetSession(ctx)
|
session, err := provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, NewDefaultUserSession(), session)
|
assert.Equal(t, NewDefaultUserSession(), session)
|
||||||
}
|
}
|
||||||
|
@ -31,22 +45,19 @@ func TestShouldInitializerSession(t *testing.T) {
|
||||||
func TestShouldUpdateSession(t *testing.T) {
|
func TestShouldUpdateSession(t *testing.T) {
|
||||||
ctx := &fasthttp.RequestCtx{}
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
|
||||||
configuration := schema.SessionConfiguration{}
|
provider, err := newTestSession()
|
||||||
configuration.Domain = testDomain
|
assert.NoError(t, err)
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
|
|
||||||
provider := NewProvider(configuration, nil)
|
|
||||||
session, _ := provider.GetSession(ctx)
|
session, _ := provider.GetSession(ctx)
|
||||||
|
|
||||||
session.Username = testUsername
|
session.Username = testUsername
|
||||||
session.AuthenticationLevel = authentication.TwoFactor
|
session.AuthenticationLevel = authentication.TwoFactor
|
||||||
|
|
||||||
err := provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, UserSession{
|
assert.Equal(t, UserSession{
|
||||||
Username: testUsername,
|
Username: testUsername,
|
||||||
|
@ -56,26 +67,23 @@ func TestShouldUpdateSession(t *testing.T) {
|
||||||
|
|
||||||
func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
||||||
ctx := &fasthttp.RequestCtx{}
|
ctx := &fasthttp.RequestCtx{}
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
|
|
||||||
timeOneFactor := time.Unix(1625048140, 0)
|
timeOneFactor := time.Unix(1625048140, 0)
|
||||||
timeTwoFactor := time.Unix(1625048150, 0)
|
timeTwoFactor := time.Unix(1625048150, 0)
|
||||||
timeZeroFactor := time.Unix(0, 0)
|
timeZeroFactor := time.Unix(0, 0)
|
||||||
|
|
||||||
configuration.Domain = testDomain
|
provider, err := newTestSession()
|
||||||
configuration.Name = testName
|
assert.NoError(t, err)
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
|
|
||||||
provider := NewProvider(configuration, nil)
|
|
||||||
session, _ := provider.GetSession(ctx)
|
session, _ := provider.GetSession(ctx)
|
||||||
|
|
||||||
session.SetOneFactor(timeOneFactor, &authentication.UserDetails{Username: testUsername}, false)
|
session.SetOneFactor(timeOneFactor, &authentication.UserDetails{Username: testUsername}, false)
|
||||||
|
|
||||||
err := provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
authAt, err := session.AuthenticatedTime(authorization.OneFactor)
|
authAt, err := session.AuthenticatedTime(authorization.OneFactor)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -100,10 +108,10 @@ func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
||||||
session.SetTwoFactorDuo(timeTwoFactor)
|
session.SetTwoFactorDuo(timeTwoFactor)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, UserSession{
|
assert.Equal(t, UserSession{
|
||||||
Username: testUsername,
|
Username: testUsername,
|
||||||
|
@ -129,26 +137,23 @@ func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
||||||
|
|
||||||
func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
ctx := &fasthttp.RequestCtx{}
|
ctx := &fasthttp.RequestCtx{}
|
||||||
configuration := schema.SessionConfiguration{}
|
|
||||||
|
|
||||||
timeOneFactor := time.Unix(1625048140, 0)
|
timeOneFactor := time.Unix(1625048140, 0)
|
||||||
timeTwoFactor := time.Unix(1625048150, 0)
|
timeTwoFactor := time.Unix(1625048150, 0)
|
||||||
timeZeroFactor := time.Unix(0, 0)
|
timeZeroFactor := time.Unix(0, 0)
|
||||||
|
|
||||||
configuration.Domain = testDomain
|
provider, err := newTestSession()
|
||||||
configuration.Name = testName
|
assert.NoError(t, err)
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
|
|
||||||
provider := NewProvider(configuration, nil)
|
|
||||||
session, _ := provider.GetSession(ctx)
|
session, _ := provider.GetSession(ctx)
|
||||||
|
|
||||||
session.SetOneFactor(timeOneFactor, &authentication.UserDetails{Username: testUsername}, false)
|
session.SetOneFactor(timeOneFactor, &authentication.UserDetails{Username: testUsername}, false)
|
||||||
|
|
||||||
err := provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
authAt, err := session.AuthenticatedTime(authorization.OneFactor)
|
authAt, err := session.AuthenticatedTime(authorization.OneFactor)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -173,10 +178,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true}, session.AuthenticationMethodRefs)
|
assert.Equal(t, oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true}, session.AuthenticationMethodRefs)
|
||||||
assert.True(t, session.AuthenticationMethodRefs.MultiFactorAuthentication())
|
assert.True(t, session.AuthenticationMethodRefs.MultiFactorAuthentication())
|
||||||
|
@ -196,10 +201,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
||||||
|
@ -208,10 +213,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
||||||
|
@ -220,10 +225,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
||||||
|
@ -232,10 +237,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
||||||
|
@ -244,10 +249,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
||||||
|
@ -256,10 +261,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
||||||
|
@ -268,10 +273,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session.SetTwoFactorTOTP(timeTwoFactor)
|
session.SetTwoFactorTOTP(timeTwoFactor)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
||||||
|
@ -280,10 +285,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
session.SetTwoFactorTOTP(timeTwoFactor)
|
session.SetTwoFactorTOTP(timeTwoFactor)
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = provider.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
session, err = provider.GetSession(ctx)
|
session, err = provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
||||||
|
@ -292,31 +297,28 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||||
|
|
||||||
func TestShouldDestroySessionAndWipeSessionData(t *testing.T) {
|
func TestShouldDestroySessionAndWipeSessionData(t *testing.T) {
|
||||||
ctx := &fasthttp.RequestCtx{}
|
ctx := &fasthttp.RequestCtx{}
|
||||||
configuration := schema.SessionConfiguration{}
|
domainSession, err := newTestSession()
|
||||||
configuration.Domain = testDomain
|
assert.NoError(t, err)
|
||||||
configuration.Name = testName
|
|
||||||
configuration.Expiration = testExpiration
|
|
||||||
|
|
||||||
provider := NewProvider(configuration, nil)
|
session, err := domainSession.GetSession(ctx)
|
||||||
session, err := provider.GetSession(ctx)
|
assert.NoError(t, err)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
session.Username = testUsername
|
session.Username = testUsername
|
||||||
session.AuthenticationLevel = authentication.TwoFactor
|
session.AuthenticationLevel = authentication.TwoFactor
|
||||||
|
|
||||||
err = provider.SaveSession(ctx, session)
|
err = domainSession.SaveSession(ctx, session)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
newUserSession, err := provider.GetSession(ctx)
|
newUserSession, err := domainSession.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, testUsername, newUserSession.Username)
|
assert.Equal(t, testUsername, newUserSession.Username)
|
||||||
assert.Equal(t, authentication.TwoFactor, newUserSession.AuthenticationLevel)
|
assert.Equal(t, authentication.TwoFactor, newUserSession.AuthenticationLevel)
|
||||||
|
|
||||||
err = provider.DestroySession(ctx)
|
err = domainSession.DestroySession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
newUserSession, err = provider.GetSession(ctx)
|
newUserSession, err = domainSession.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "", newUserSession.Username)
|
assert.Equal(t, "", newUserSession.Username)
|
||||||
assert.Equal(t, authentication.NotAuthenticated, newUserSession.AuthenticationLevel)
|
assert.Equal(t, authentication.NotAuthenticated, newUserSession.AuthenticationLevel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
fasthttpsession "github.com/fasthttp/session/v2"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Session a session provider.
|
||||||
|
type Session struct {
|
||||||
|
Config schema.SessionCookieConfiguration
|
||||||
|
|
||||||
|
sessionHolder *fasthttpsession.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSession return the user session from a request.
|
||||||
|
func (p *Session) GetSession(ctx *fasthttp.RequestCtx) (UserSession, error) {
|
||||||
|
store, err := p.sessionHolder.Get(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return NewDefaultUserSession(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
userSessionJSON, ok := store.Get(userSessionStorerKey).([]byte)
|
||||||
|
|
||||||
|
// If userSession is not yet defined we create the new session with default values
|
||||||
|
// and save it in the store.
|
||||||
|
if !ok {
|
||||||
|
userSession := NewDefaultUserSession()
|
||||||
|
|
||||||
|
store.Set(userSessionStorerKey, userSession)
|
||||||
|
|
||||||
|
return userSession, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var userSession UserSession
|
||||||
|
err = json.Unmarshal(userSessionJSON, &userSession)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return NewDefaultUserSession(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return userSession, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveSession save the user session.
|
||||||
|
func (p *Session) SaveSession(ctx *fasthttp.RequestCtx, userSession UserSession) error {
|
||||||
|
store, err := p.sessionHolder.Get(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
userSessionJSON, err := json.Marshal(userSession)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
store.Set(userSessionStorerKey, userSessionJSON)
|
||||||
|
|
||||||
|
err = p.sessionHolder.Save(ctx, store)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegenerateSession regenerate a session ID.
|
||||||
|
func (p *Session) RegenerateSession(ctx *fasthttp.RequestCtx) error {
|
||||||
|
err := p.sessionHolder.Regenerate(ctx)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroySession destroy a session ID and delete the cookie.
|
||||||
|
func (p *Session) DestroySession(ctx *fasthttp.RequestCtx) error {
|
||||||
|
return p.sessionHolder.Destroy(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateExpiration update the expiration of the cookie and session.
|
||||||
|
func (p *Session) UpdateExpiration(ctx *fasthttp.RequestCtx, expiration time.Duration) error {
|
||||||
|
store, err := p.sessionHolder.Get(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.SetExpiration(expiration)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.sessionHolder.Save(ctx, store)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExpiration get the expiration of the current session.
|
||||||
|
func (p *Session) GetExpiration(ctx *fasthttp.RequestCtx) (time.Duration, error) {
|
||||||
|
store, err := p.sessionHolder.Get(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return time.Duration(0), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.GetExpiration(), nil
|
||||||
|
}
|
|
@ -3,8 +3,7 @@ package session
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
session "github.com/fasthttp/session/v2"
|
"github.com/fasthttp/session/v2"
|
||||||
"github.com/fasthttp/session/v2/providers/redis"
|
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
|
@ -14,8 +13,6 @@ import (
|
||||||
// ProviderConfig is the configuration used to create the session provider.
|
// ProviderConfig is the configuration used to create the session provider.
|
||||||
type ProviderConfig struct {
|
type ProviderConfig struct {
|
||||||
config session.Config
|
config session.Config
|
||||||
redisConfig *redis.Config
|
|
||||||
redisSentinelConfig *redis.FailoverConfig
|
|
||||||
providerName string
|
providerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -23,7 +23,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -20,10 +20,13 @@ authentication_backend:
|
||||||
|
|
||||||
session:
|
session:
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
domain: example.com
|
cookies:
|
||||||
|
- name: 'authelia_session'
|
||||||
|
domain: 'example.com'
|
||||||
|
authelia_url: 'https://login.example.com'
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -24,7 +24,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -24,7 +24,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -24,7 +24,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
||||||
storage:
|
storage:
|
||||||
|
|
|
@ -21,10 +21,12 @@ authentication_backend:
|
||||||
|
|
||||||
session:
|
session:
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
domain: example.com
|
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
cookies:
|
||||||
|
- name: 'authelia_session'
|
||||||
|
domain: 'example.com'
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -23,7 +23,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -102,7 +102,7 @@ session:
|
||||||
- host: redis-sentinel-2
|
- host: redis-sentinel-2
|
||||||
port: 26379
|
port: 26379
|
||||||
|
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
regulation:
|
regulation:
|
||||||
max_retries: 3
|
max_retries: 3
|
||||||
|
|
|
@ -38,7 +38,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
encryption_key: a_not_so_secure_encryption_key
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
|
|
@ -24,7 +24,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
||||||
storage:
|
storage:
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
---
|
||||||
|
###############################################################
|
||||||
|
# Authelia minimal configuration #
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
jwt_secret: unsecure_secret
|
||||||
|
theme: auto
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: 9091
|
||||||
|
tls:
|
||||||
|
certificate: /config/ssl/cert.pem
|
||||||
|
key: /config/ssl/key.pem
|
||||||
|
|
||||||
|
telemetry:
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
address: tcp://0.0.0.0:9959
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: debug
|
||||||
|
|
||||||
|
authentication_backend:
|
||||||
|
file:
|
||||||
|
path: /config/users.yml
|
||||||
|
|
||||||
|
session:
|
||||||
|
secret: unsecure_session_secret
|
||||||
|
expiration: 3600
|
||||||
|
inactivity: 300
|
||||||
|
remember_me: 1y
|
||||||
|
cookies:
|
||||||
|
- name: 'authelia_session'
|
||||||
|
domain: 'example.com'
|
||||||
|
- name: 'example2_session'
|
||||||
|
domain: 'example2.com'
|
||||||
|
authelia_url: 'https://login.example2.com'
|
||||||
|
remember_me: -1
|
||||||
|
- name: 'authelia_session'
|
||||||
|
domain: 'example3.com'
|
||||||
|
authelia_url: 'https://login.example3.com'
|
||||||
|
|
||||||
|
storage:
|
||||||
|
encryption_key: a_not_so_secure_encryption_key
|
||||||
|
local:
|
||||||
|
path: /config/db.sqlite
|
||||||
|
|
||||||
|
totp:
|
||||||
|
issuer: example.com
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
default_policy: deny
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# First cookie domain
|
||||||
|
- domain: singlefactor.example.com
|
||||||
|
policy: one_factor
|
||||||
|
|
||||||
|
- domain: public.example.com
|
||||||
|
policy: bypass
|
||||||
|
|
||||||
|
- domain: secure.example.com
|
||||||
|
policy: bypass
|
||||||
|
methods:
|
||||||
|
- OPTIONS
|
||||||
|
|
||||||
|
- domain: secure.example.com
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: "*.example.com"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/users/john/.*$"
|
||||||
|
subject: "user:john"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/users/harry/.*$"
|
||||||
|
subject: "user:harry"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: "*.mail.example.com"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/users/bob/.*$"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Second cookie domain
|
||||||
|
- domain: singlefactor.example2.com
|
||||||
|
policy: one_factor
|
||||||
|
|
||||||
|
- domain: public.example2.com
|
||||||
|
policy: bypass
|
||||||
|
|
||||||
|
- domain: secure.example2.com
|
||||||
|
policy: bypass
|
||||||
|
methods:
|
||||||
|
- OPTIONS
|
||||||
|
|
||||||
|
- domain: secure.example2.com
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: "*.example2.com"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example2.com
|
||||||
|
resources:
|
||||||
|
- "^/users/john/.*$"
|
||||||
|
subject: "user:john"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example2.com
|
||||||
|
resources:
|
||||||
|
- "^/users/harry/.*$"
|
||||||
|
subject: "user:harry"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: "*.mail.example2.com"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example2.com
|
||||||
|
resources:
|
||||||
|
- "^/users/bob/.*$"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Third cookie domain
|
||||||
|
- domain: singlefactor.example3.com
|
||||||
|
policy: one_factor
|
||||||
|
|
||||||
|
- domain: public.example3.com
|
||||||
|
policy: bypass
|
||||||
|
|
||||||
|
- domain: secure.example3.com
|
||||||
|
policy: bypass
|
||||||
|
methods:
|
||||||
|
- OPTIONS
|
||||||
|
|
||||||
|
- domain: secure.example3.com
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: "*.example3.com"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example3.com
|
||||||
|
resources:
|
||||||
|
- "^/users/john/.*$"
|
||||||
|
subject: "user:john"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example3.com
|
||||||
|
resources:
|
||||||
|
- "^/users/harry/.*$"
|
||||||
|
subject: "user:harry"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: "*.mail.example3.com"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: dev.example3.com
|
||||||
|
resources:
|
||||||
|
- "^/users/bob/.*$"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
|
||||||
|
regulation:
|
||||||
|
# Set it to 0 to disable max_retries.
|
||||||
|
max_retries: 3
|
||||||
|
# The user is banned if the authentication failed `max_retries` times in a `find_time` seconds window.
|
||||||
|
find_time: 300
|
||||||
|
# The length of time before a banned user can login again.
|
||||||
|
ban_time: 900
|
||||||
|
|
||||||
|
notifier:
|
||||||
|
smtp:
|
||||||
|
host: smtp
|
||||||
|
port: 1025
|
||||||
|
sender: admin@example.com
|
||||||
|
disable_require_tls: true
|
||||||
|
ntp:
|
||||||
|
## NTP server address
|
||||||
|
address: "time.cloudflare.com:123"
|
||||||
|
## ntp version
|
||||||
|
version: 4
|
||||||
|
## "maximum desynchronization" is the allowed offset time between the host and the ntp server
|
||||||
|
max_desync: 3s
|
||||||
|
## You can enable or disable the NTP synchronization check on startup
|
||||||
|
disable_startup_check: false
|
||||||
|
|
||||||
|
password_policy:
|
||||||
|
standard:
|
||||||
|
# Enables standard password Policy
|
||||||
|
enabled: false
|
||||||
|
min_length: 8
|
||||||
|
max_length: 0
|
||||||
|
require_uppercase: true
|
||||||
|
require_lowercase: true
|
||||||
|
require_number: true
|
||||||
|
require_special: true
|
||||||
|
zxcvbn:
|
||||||
|
## zxcvbn: uses zxcvbn for password strength checking (see: https://github.com/dropbox/zxcvbn)
|
||||||
|
## Note that the zxcvbn option does not prohibit the user from using a weak password,
|
||||||
|
## it only offers feedback about the strength of the password they are entering.
|
||||||
|
## if you need to enforce password rules, you should use `mode=classic`
|
||||||
|
enabled: false
|
||||||
|
...
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
authelia-backend:
|
||||||
|
volumes:
|
||||||
|
- './MultiCookieDomain/configuration.yml:/config/configuration.yml:ro'
|
||||||
|
- './MultiCookieDomain/users.yml:/config/users.yml'
|
||||||
|
- './common/ssl:/config/ssl:ro'
|
||||||
|
...
|
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
###############################################################
|
||||||
|
# Users Database #
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
# This file can be used if you do not have an LDAP set up.
|
||||||
|
|
||||||
|
# List of users
|
||||||
|
users:
|
||||||
|
john:
|
||||||
|
displayname: "John Doe"
|
||||||
|
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length
|
||||||
|
email: john.doe@authelia.com
|
||||||
|
groups:
|
||||||
|
- admins
|
||||||
|
- dev
|
||||||
|
|
||||||
|
harry:
|
||||||
|
displayname: "Harry Potter"
|
||||||
|
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length
|
||||||
|
email: harry.potter@authelia.com
|
||||||
|
groups: []
|
||||||
|
|
||||||
|
bob:
|
||||||
|
displayname: "Bob Dylan"
|
||||||
|
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length
|
||||||
|
email: bob.dylan@authelia.com
|
||||||
|
groups:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
james:
|
||||||
|
displayname: "James Dean"
|
||||||
|
password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" # yamllint disable-line rule:line-length
|
||||||
|
email: james.dean@authelia.com
|
||||||
|
...
|
|
@ -25,7 +25,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
||||||
storage:
|
storage:
|
||||||
|
|
|
@ -23,7 +23,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
# Configuration of the storage backend used to store data and secrets. i.e. totp data
|
||||||
storage:
|
storage:
|
||||||
|
|
|
@ -16,10 +16,13 @@ authentication_backend:
|
||||||
|
|
||||||
session:
|
session:
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
domain: example.com
|
|
||||||
|
cookies:
|
||||||
|
- domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
|
|
||||||
# We use redis here to keep the users authenticated when Authelia restarts
|
# We use redis here to keep the users authenticated when Authelia restarts
|
||||||
# It eases development.
|
# It eases development.
|
||||||
redis:
|
redis:
|
||||||
|
|
|
@ -19,7 +19,7 @@ session:
|
||||||
domain: example.com
|
domain: example.com
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me: 1y
|
||||||
# We use redis here to keep the users authenticated when Authelia restarts
|
# We use redis here to keep the users authenticated when Authelia restarts
|
||||||
# It eases development.
|
# It eases development.
|
||||||
redis:
|
redis:
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue