Merge remote-tracking branch 'origin/master' into feat-settings-ui
commit
bd279900ca
|
@ -23,7 +23,13 @@
|
|||
"maintenance",
|
||||
"question",
|
||||
"review",
|
||||
"test"
|
||||
"test",
|
||||
"mentoring",
|
||||
"infra",
|
||||
"design",
|
||||
"userTesting",
|
||||
"tool",
|
||||
"research"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -38,7 +44,13 @@
|
|||
"maintenance",
|
||||
"question",
|
||||
"review",
|
||||
"test"
|
||||
"test",
|
||||
"mentoring",
|
||||
"infra",
|
||||
"design",
|
||||
"userTesting",
|
||||
"tool",
|
||||
"research"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -53,7 +65,13 @@
|
|||
"maintenance",
|
||||
"question",
|
||||
"review",
|
||||
"test"
|
||||
"test",
|
||||
"mentoring",
|
||||
"infra",
|
||||
"design",
|
||||
"userTesting",
|
||||
"tool",
|
||||
"research"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -649,7 +667,14 @@
|
|||
"profile": "https://github.com/mind-ar",
|
||||
"contributions": [
|
||||
"code",
|
||||
"translation"
|
||||
"translation",
|
||||
"doc",
|
||||
"bug",
|
||||
"design",
|
||||
"test",
|
||||
"review",
|
||||
"research",
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -834,6 +859,63 @@
|
|||
"contributions": [
|
||||
"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
|
||||
|
|
|
@ -7,7 +7,7 @@ trim_trailing_whitespace = true
|
|||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{sh,yml,yaml}]
|
||||
[*.{html,sh,yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"npm"
|
||||
],
|
||||
"kubernetes": {
|
||||
"fileMatch": ["kube/.+\\.yml$"],
|
||||
"fileMatch": ["kube/.+\\.yml$"]
|
||||
},
|
||||
"labels": [
|
||||
"dependencies"
|
||||
|
@ -30,7 +30,6 @@
|
|||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": ["digest", "minor", "patch"],
|
||||
"matchCurrentVersion": "!/^0/",
|
||||
"automerge": true,
|
||||
"automergeType": "pr",
|
||||
"platformAutomerge": true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# ===================================
|
||||
# ===== Authelia official image =====
|
||||
# ===================================
|
||||
FROM alpine:3.17.0
|
||||
FROM alpine:3.17.1
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
|
|
@ -15,7 +15,7 @@ RUN yarn global add pnpm && \
|
|||
# =======================================
|
||||
# ===== 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
|
||||
|
||||
|
@ -46,7 +46,7 @@ RUN \
|
|||
# ===================================
|
||||
# ===== Authelia official image =====
|
||||
# ===================================
|
||||
FROM alpine:3.17.0
|
||||
FROM alpine:3.17.1
|
||||
|
||||
RUN apk --no-cache add ca-certificates tzdata
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ RUN yarn install --frozen-lockfile && yarn build
|
|||
# =======================================
|
||||
# ===== 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
|
||||
|
||||
|
@ -43,7 +43,7 @@ RUN \
|
|||
# ===================================
|
||||
# ===== Authelia official image =====
|
||||
# ===================================
|
||||
FROM alpine:3.17.0
|
||||
FROM alpine:3.17.1
|
||||
|
||||
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>
|
||||
<tbody>
|
||||
<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/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/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/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> <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> <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/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>
|
||||
|
@ -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="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/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="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>
|
||||
|
@ -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/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/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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -79,7 +79,7 @@ const (
|
|||
|
||||
type labelPriority int
|
||||
|
||||
//nolint:deadcode // Kept for future use.
|
||||
//nolint:deadcode,varcheck // Kept for future use.
|
||||
const (
|
||||
labelPriorityCritical labelPriority = iota
|
||||
labelPriorityHigh
|
||||
|
@ -122,7 +122,7 @@ func (s labelStatus) String() string {
|
|||
|
||||
type labelType int
|
||||
|
||||
//nolint:deadcode // Kept for future use.
|
||||
//nolint:deadcode,varcheck // Kept for future use.
|
||||
const (
|
||||
labelTypeFeature labelType = iota
|
||||
labelTypeBugUnconfirmed
|
||||
|
|
|
@ -114,6 +114,30 @@ var hostEntries = []HostEntry{
|
|||
{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-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) {
|
||||
|
|
|
@ -662,38 +662,76 @@ access_control:
|
|||
## The session cookies identify the user once logged in.
|
||||
## The available providers are: `memory`, `redis`. Memory is the provider unless redis is defined.
|
||||
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.
|
||||
name: authelia_session
|
||||
# name: 'authelia_session'
|
||||
|
||||
## The domain to protect.
|
||||
## Note: the authenticator must also be in that domain.
|
||||
## If empty, the cookie is restricted to the subdomain of the issuer.
|
||||
domain: example.com
|
||||
## Note: the Authelia portal must also be in that domain.
|
||||
# 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.
|
||||
## 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.
|
||||
## 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.
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
## 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
|
||||
## 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'
|
||||
|
||||
## The time before the cookie expires and the session is destroyed if remember me IS selected.
|
||||
## Value of -1 disables remember me.
|
||||
remember_me_duration: 1M
|
||||
## The time before the session cookie expires and the session is destroyed if remember me IS NOT selected by the
|
||||
## user.
|
||||
# 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
|
||||
|
|
|
@ -5,5 +5,5 @@ Please:
|
|||
- 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)
|
||||
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
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
|
||||
the environment variable is completely ignored.
|
||||
|
||||
|
@ -164,7 +164,7 @@ authelia --config config.yml --config.experimental.filters expand-env,template
|
|||
```
|
||||
|
||||
```text
|
||||
X_AUTHELIA_CONFIG_EXPERIMENTAL_FILTERS=expand-env,template
|
||||
X_AUTHELIA_CONFIG_FILTERS=expand-env,template
|
||||
```
|
||||
|
||||
### Expand Environment Variable Filter
|
||||
|
|
|
@ -22,7 +22,7 @@ describes the implementation of this. You can use this implementation in various
|
|||
* session:
|
||||
* expiration
|
||||
* inactivity
|
||||
* remember_me_duration
|
||||
* remember_me
|
||||
* regulation:
|
||||
* ban_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].
|
||||
|
||||
[domain_regex]: #domainregex
|
||||
[domain_regex]: #domain_regex
|
||||
|
||||
##### Examples
|
||||
|
||||
|
@ -339,7 +339,7 @@ access_control:
|
|||
{{< 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
|
||||
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
|
||||
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.*
|
||||
|
@ -360,7 +360,7 @@ for administrators to tune the security to their specific needs if desired.
|
|||
|
||||
##### 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.*
|
||||
|
||||
```yaml
|
||||
|
@ -485,7 +485,7 @@ access_control:
|
|||
## Policies
|
||||
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
performed 2FA then they will be allowed to access the resource.
|
||||
|
||||
[one_factor]: #onefactor
|
||||
[one_factor]: #one_factor
|
||||
|
||||
### two_factor
|
||||
|
||||
This policy requires the user to complete 2FA successfully. This is currently the highest level of authentication
|
||||
policy available.
|
||||
|
||||
[two_factor]: #twofactor
|
||||
[two_factor]: #two_factor
|
||||
|
||||
## Rule Matching
|
||||
|
||||
|
@ -554,7 +554,7 @@ a match for that request.
|
|||
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
|
||||
|
||||
|
@ -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
|
||||
[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
|
||||
|
||||
|
|
|
@ -25,13 +25,21 @@ authenticated user and can then order the reverse proxy to let the request pass
|
|||
|
||||
```yaml
|
||||
session:
|
||||
secret: insecure_session_secret
|
||||
|
||||
name: authelia_session
|
||||
same_site: lax
|
||||
inactivity: 5m
|
||||
expiration: 1h
|
||||
remember_me: 1M
|
||||
|
||||
cookies:
|
||||
- name: authelia_session
|
||||
domain: example.com
|
||||
same_site: lax
|
||||
secret: unsecure_session_secret
|
||||
expiration: 1h
|
||||
inactivity: 5m
|
||||
remember_me_duration: 1M
|
||||
expiration: 1h
|
||||
remember_me: 1d
|
||||
```
|
||||
|
||||
## Providers
|
||||
|
@ -50,34 +58,6 @@ providers are recommended.
|
|||
|
||||
## 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
|
||||
|
||||
{{< 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
|
||||
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
|
||||
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
||||
_**Deprecation Notice:** This option is deprecated. See the [cookies](#cookies) section instead._
|
||||
|
||||
The period of time before the cookie expires and the session is destroyed. This is overriden by
|
||||
[remember_me_duration](#remembermeduration) when the remember me box is checked.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
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.
|
||||
The default `inactivity` value for all [cookies](#cookies) configurations.
|
||||
|
||||
### 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" >}}
|
||||
|
||||
*__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 `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
|
||||
this to `-1` disables this feature entirely.
|
||||
this to `-1` disables this feature entirely for this session cookie domain.
|
||||
|
||||
## Security
|
||||
|
||||
|
|
|
@ -51,8 +51,8 @@ for, and the structure it must have.
|
|||
│ └─⫸ Commit Scope: api|autheliabot|authentication|authorization|buildkite|bundler|cmd|
|
||||
│ codecov|commands|configuration|deps|docker|duo|go|golangci-lint|
|
||||
│ handlers|logging|metrics|middlewares|mocks|model|notification|npm|ntp|
|
||||
│ oidc|regulation|renovate|reviewdog|server|session|storage|suites|
|
||||
│ templates|totp|utils|web
|
||||
│ oidc|random|regulation|renovate|reviewdog|server|session|storage|
|
||||
│ suites|templates|totp|utils|web
|
||||
│
|
||||
└─⫸ Commit Type: build|ci|docs|feat|fix|i18n|perf|refactor|release|revert|test
|
||||
```
|
||||
|
@ -93,6 +93,7 @@ commit messages).
|
|||
* notification
|
||||
* ntp
|
||||
* oidc
|
||||
* random
|
||||
* regulation
|
||||
* server
|
||||
* session
|
||||
|
|
|
@ -43,7 +43,7 @@ The following steps will allow you to run the website on the localhost and view
|
|||
```bash
|
||||
git clone https://github.com/authelia/authelia.git
|
||||
cd authelia/docs
|
||||
npm run install
|
||||
npm install
|
||||
npm run start
|
||||
```
|
||||
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.
|
||||
|
||||
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
|
||||
[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
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.20.7",
|
||||
"@babel/core": "7.20.7",
|
||||
"@babel/core": "7.20.12",
|
||||
"@babel/preset-env": "7.20.2",
|
||||
"@fullhuman/postcss-purgecss": "5.0.0",
|
||||
"@hyas/images": "0.3.2",
|
||||
|
@ -49,7 +49,7 @@
|
|||
"bootstrap": "5.2.3",
|
||||
"bootstrap-icons": "1.10.3",
|
||||
"clipboard": "2.0.11",
|
||||
"eslint": "8.31.0",
|
||||
"eslint": "8.32.0",
|
||||
"exec-bin": "1.0.0",
|
||||
"flexsearch": "0.7.31",
|
||||
"highlight.js": "11.7.0",
|
||||
|
@ -60,7 +60,7 @@
|
|||
"markdownlint-cli2": "0.6.0",
|
||||
"netlify-plugin-submit-sitemap": "0.4.0",
|
||||
"node-fetch": "3.3.0",
|
||||
"postcss": "8.4.20",
|
||||
"postcss": "8.4.21",
|
||||
"postcss-cli": "10.1.0",
|
||||
"purgecss-whitelister": "2.4.0",
|
||||
"shx": "0.3.4",
|
||||
|
@ -68,6 +68,6 @@
|
|||
"stylelint-config-standard-scss": "6.1.0"
|
||||
},
|
||||
"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 #
|
||||
###############################################################
|
||||
|
||||
# This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE
|
||||
jwt_secret: a_very_important_secret
|
||||
default_redirection_url: https://public.example.com
|
||||
|
||||
|
@ -12,7 +13,6 @@ server:
|
|||
|
||||
log:
|
||||
level: debug
|
||||
# This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE
|
||||
|
||||
totp:
|
||||
issuer: authelia.com
|
||||
|
@ -39,12 +39,14 @@ access_control:
|
|||
policy: two_factor
|
||||
|
||||
session:
|
||||
name: authelia_session
|
||||
# This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE
|
||||
secret: unsecure_session_secret
|
||||
|
||||
cookies:
|
||||
- name: authelia_session
|
||||
domain: example.com # Should match whatever your root protected domain is
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
domain: example.com # Should match whatever your root protected domain is
|
||||
|
||||
redis:
|
||||
host: redis
|
||||
|
|
|
@ -31,11 +31,13 @@ access_control:
|
|||
policy: two_factor
|
||||
|
||||
session:
|
||||
name: authelia_session
|
||||
secret: unsecure_session_secret
|
||||
|
||||
cookies:
|
||||
- name: authelia_session
|
||||
domain: example.com # Should match whatever your root protected domain is
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
domain: example.com # Should match whatever your root protected domain is
|
||||
|
||||
regulation:
|
||||
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/deckarep/golang-set/v2 v2.1.0
|
||||
github.com/duosecurity/duo_api_golang v0.0.0-20221117185402-091daa09e19d
|
||||
github.com/fasthttp/router v1.4.14
|
||||
github.com/fasthttp/session/v2 v2.4.13
|
||||
github.com/fasthttp/router v1.4.15
|
||||
github.com/fasthttp/session/v2 v2.4.16
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4
|
||||
github.com/go-crypt/crypt v0.2.3
|
||||
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-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/mock v1.6.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2
|
||||
github.com/jackc/pgx/v5 v5.2.0
|
||||
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/mitchellh/mapstructure v1.5.0
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/ory/fosite v0.44.0
|
||||
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/pkg/errors v0.9.1
|
||||
github.com/pquerna/otp v1.4.0
|
||||
|
@ -38,8 +38,8 @@ require (
|
|||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/trustelem/zxcvbn v1.0.1
|
||||
github.com/valyala/fasthttp v1.43.0
|
||||
github.com/wneessen/go-mail v0.3.7
|
||||
github.com/valyala/fasthttp v1.44.0
|
||||
github.com/wneessen/go-mail v0.3.8
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/term v0.4.0
|
||||
golang.org/x/text v0.6.0
|
||||
|
@ -88,31 +88,30 @@ require (
|
|||
github.com/ory/viper v1.7.5 // indirect
|
||||
github.com/pborman/uuid v1.2.1 // 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/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.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/spf13/afero v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // 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/x448/float16 v0.8.4 // indirect
|
||||
github.com/ysmood/goob v0.4.0 // indirect
|
||||
github.com/ysmood/gson v0.7.3 // indirect
|
||||
github.com/ysmood/leakless v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/net v0.5.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // 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/genproto v0.0.0-20221025140454-527a21cfbd71 // 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/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/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/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
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/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||
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.14/go.mod h1:+svLaOvqF9Lc0yjX9aHAD4NUMf+mggLPOT4UMdS6fjM=
|
||||
github.com/fasthttp/session/v2 v2.4.13 h1:I/j3w8UrXX1haXE+iraAbQuGihNVeTq6b8sp6L3ZJ6Q=
|
||||
github.com/fasthttp/session/v2 v2.4.13/go.mod h1:bAE6Bjl6ofQbkOpqcSuOVt/1R1LnbNLnFMHjGQcYP5M=
|
||||
github.com/fasthttp/router v1.4.15 h1:ERaILezYX6ks1I+Z2v5qY4vqiQKnujauo9nV6M+HIOg=
|
||||
github.com/fasthttp/router v1.4.15/go.mod h1:NFNlTCilbRVkeLc+E5JDkcxUdkpiJGKDL8Zy7Ey2JTI=
|
||||
github.com/fasthttp/session/v2 v2.4.16 h1:JRvuEqr/+/cNMBkhGZN118FurLh6paUGscwJr26TxAQ=
|
||||
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.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
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-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-rod/rod v0.112.2 h1:dwauKYC/H2em8/BcGk3gC0LTzZHf5MIDKf2DVM4z9gU=
|
||||
github.com/go-rod/rod v0.112.2/go.mod h1:ElViL9ABbcshNQw93+11FrYRH92RRhMKleuILo6+5V0=
|
||||
github.com/go-rod/rod v0.112.3 h1:xbSaA9trZ8v/+eJRGOM6exK1RCsLPwwnzA78vpES0gk=
|
||||
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.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
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-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/webauthn v0.5.0 h1:Tbmp37AGIhYbQmcy2hEffo3U3cgPClqvxJ7cLUnF7Rc=
|
||||
github.com/go-webauthn/webauthn v0.5.0/go.mod h1:0CBq/jNfPS9l033j4AxMk8K8MluiMsde9uGNSPFLEVE=
|
||||
github.com/go-webauthn/webauthn v0.6.0 h1:uLInMApSvBfP+vEFasNE0rnVPG++fjp7lmAIvNhe+UU=
|
||||
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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
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.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
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/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/knadh/koanf v1.4.5 h1:yKWFswTrqFc0u7jBAoERUz30+N1b1yPXU01gAPr8IrY=
|
||||
github.com/knadh/koanf v1.4.5/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
||||
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
||||
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.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
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.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.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/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
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/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE=
|
||||
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.528/go.mod h1:XBqhPZRppPHTxtsE0l0oI/B2Onf1QJtMRGPh3CpEpA0=
|
||||
github.com/ory/x v0.0.533 h1:F7kJ1C5jQv4PCmbOppK8H3V0mV2yQeOy97zCgDIHb4U=
|
||||
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/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI=
|
||||
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/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/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||
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/pkg/errors v0.8.0/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 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/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/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||
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/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
|
||||
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.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||
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/trustelem/zxcvbn v1.0.1 h1:mp4JFtzdDYGj9WYSD3KQSkwwUumWNFzXaAjckaTYpsc=
|
||||
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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
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.42.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/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q=
|
||||
github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||
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.7/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/wneessen/go-mail v0.3.8 h1:ja5D/o/RVwrtRIYFlrO7GmtcjDNeMakGQuwQRZYv0JM=
|
||||
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/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
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.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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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/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-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-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-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.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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.1/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/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
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-20180826012351-8a410e7b638d/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-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-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.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/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
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-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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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/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-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-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-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-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-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.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
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-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/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
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.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.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/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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-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-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-20201201161351-ac6f37ff4c2a/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.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
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.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
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-20191011141410-1b5146add898/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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -662,38 +662,76 @@ access_control:
|
|||
## The session cookies identify the user once logged in.
|
||||
## The available providers are: `memory`, `redis`. Memory is the provider unless redis is defined.
|
||||
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.
|
||||
name: authelia_session
|
||||
# name: 'authelia_session'
|
||||
|
||||
## The domain to protect.
|
||||
## Note: the authenticator must also be in that domain.
|
||||
## If empty, the cookie is restricted to the subdomain of the issuer.
|
||||
domain: example.com
|
||||
## Note: the Authelia portal must also be in that domain.
|
||||
# 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.
|
||||
## 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.
|
||||
## 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.
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
## 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
|
||||
## 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'
|
||||
|
||||
## The time before the cookie expires and the session is destroyed if remember me IS selected.
|
||||
## Value of -1 disables remember me.
|
||||
remember_me_duration: 1M
|
||||
## The time before the session cookie expires and the session is destroyed if remember me IS NOT selected by the
|
||||
## user.
|
||||
# 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
|
||||
|
|
|
@ -134,4 +134,11 @@ var deprecations = map[string]Deprecation{
|
|||
AutoMap: true,
|
||||
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@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) {
|
||||
|
|
|
@ -105,13 +105,23 @@ var Keys = []string{
|
|||
"authentication_backend.ldap.permit_feature_detection_failure",
|
||||
"authentication_backend.ldap.user",
|
||||
"authentication_backend.ldap.password",
|
||||
"session.secret",
|
||||
"session.name",
|
||||
"session.domain",
|
||||
"session.same_site",
|
||||
"session.secret",
|
||||
"session.expiration",
|
||||
"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.port",
|
||||
"session.redis.username",
|
||||
|
|
|
@ -2,6 +2,7 @@ package schema
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -36,24 +37,42 @@ type RedisSessionConfiguration struct {
|
|||
|
||||
// SessionConfiguration represents the configuration related to user sessions.
|
||||
type SessionConfiguration struct {
|
||||
Name string `koanf:"name"`
|
||||
Domain string `koanf:"domain"`
|
||||
SameSite string `koanf:"same_site"`
|
||||
Secret string `koanf:"secret"`
|
||||
Expiration time.Duration `koanf:"expiration"`
|
||||
Inactivity time.Duration `koanf:"inactivity"`
|
||||
RememberMeDuration time.Duration `koanf:"remember_me_duration"`
|
||||
|
||||
SessionCookieCommonConfiguration `koanf:",squash"`
|
||||
|
||||
Cookies []SessionCookieConfiguration `koanf:"cookies"`
|
||||
|
||||
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.
|
||||
var DefaultSessionConfiguration = SessionConfiguration{
|
||||
SessionCookieCommonConfiguration: SessionCookieCommonConfiguration{
|
||||
Name: "authelia_session",
|
||||
Expiration: time.Hour,
|
||||
Inactivity: time.Minute * 5,
|
||||
RememberMeDuration: time.Hour * 24 * 30,
|
||||
RememberMe: time.Hour * 24 * 30,
|
||||
SameSite: "lax",
|
||||
},
|
||||
}
|
||||
|
||||
// DefaultRedisConfiguration is the default redis configuration.
|
||||
|
|
|
@ -97,7 +97,7 @@ session:
|
|||
name: authelia_session
|
||||
expiration: 3600000 # 1 hour
|
||||
inactivity: 300000 # 5 minutes
|
||||
remember_me_duration: -1
|
||||
remember_me: -1
|
||||
domain: example.com
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
|
|
|
@ -25,9 +25,15 @@ func newDefaultConfig() schema.Configuration {
|
|||
DefaultPolicy: "two_factor",
|
||||
}
|
||||
config.Session = schema.SessionConfiguration{
|
||||
Domain: examplecom,
|
||||
Name: "authelia_session",
|
||||
Secret: "secret",
|
||||
Cookies: []schema.SessionCookieConfiguration{
|
||||
{
|
||||
SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{
|
||||
Name: "authelia_session",
|
||||
Domain: exampleDotCom,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
config.Storage.EncryptionKey = testEncryptionKey
|
||||
config.Storage.Local = &schema.LocalStorageConfiguration{
|
||||
|
@ -49,6 +55,8 @@ func TestShouldEnsureNotifierConfigIsProvided(t *testing.T) {
|
|||
ValidateConfiguration(&config, validator)
|
||||
require.Len(t, validator.Errors(), 0)
|
||||
|
||||
config = newDefaultConfig()
|
||||
|
||||
config.Notifier.SMTP = 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")
|
||||
|
||||
config = newDefaultConfig()
|
||||
|
||||
validator = schema.NewStructValidator()
|
||||
config.CertificatesDirectory = "const.go"
|
||||
|
||||
|
|
|
@ -248,7 +248,7 @@ const (
|
|||
// Session error constants.
|
||||
const (
|
||||
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'"
|
||||
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'"
|
||||
|
@ -258,6 +258,16 @@ const (
|
|||
|
||||
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"
|
||||
|
||||
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.
|
||||
|
@ -363,7 +373,10 @@ var (
|
|||
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{
|
||||
"authentication_backend.ldap.skip_verify": "authentication_backend.ldap.tls.skip_verify",
|
||||
|
|
|
@ -12,5 +12,5 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
examplecom = "example.com"
|
||||
exampleDotCom = "example.com"
|
||||
)
|
||||
|
|
|
@ -257,7 +257,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
|
|||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
},
|
||||
SectorIdentifier: mustParseURL(examplecom),
|
||||
SectorIdentifier: mustParseURL(exampleDotCom),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -289,12 +289,12 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
|
|||
},
|
||||
},
|
||||
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", examplecom, "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", examplecom, "fragment", "fragment"),
|
||||
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "username", "user"),
|
||||
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", examplecom, "password"),
|
||||
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", exampleDotCom, "path", "/path"),
|
||||
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", exampleDotCom, "fragment", "fragment"),
|
||||
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", exampleDotCom, "password"),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -23,7 +23,7 @@ func (suite *NotifierSuite) SetupTest() {
|
|||
Username: "john",
|
||||
Password: "password",
|
||||
Sender: mail.Address{Name: "Authelia", Address: "authelia@example.com"},
|
||||
Host: examplecom,
|
||||
Host: exampleDotCom,
|
||||
Port: 25,
|
||||
}
|
||||
suite.config.FileSystem = nil
|
||||
|
@ -78,7 +78,7 @@ func (suite *NotifierSuite) TestSMTPShouldSetTLSDefaults() {
|
|||
suite.Assert().Len(suite.validator.Warnings(), 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().False(suite.config.SMTP.TLS.SkipVerify)
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ func (suite *NotifierSuite) TestSMTPShouldDefaultTLSServerNameToHost() {
|
|||
}
|
||||
|
||||
func (suite *NotifierSuite) TestSMTPShouldErrorOnSSL30() {
|
||||
suite.config.SMTP.Host = examplecom
|
||||
suite.config.SMTP.Host = exampleDotCom
|
||||
suite.config.SMTP.TLS = &schema.TLSConfig{
|
||||
MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ func (suite *NotifierSuite) TestSMTPShouldErrorOnSSL30() {
|
|||
}
|
||||
|
||||
func (suite *NotifierSuite) TestSMTPShouldErrorOnTLSMinVerGreaterThanMaxVer() {
|
||||
suite.config.SMTP.Host = examplecom
|
||||
suite.config.SMTP.Host = exampleDotCom
|
||||
suite.config.SMTP.TLS = &schema.TLSConfig{
|
||||
MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
|
||||
MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS10},
|
||||
|
@ -140,7 +140,7 @@ func (suite *NotifierSuite) TestSMTPShouldErrorOnTLSMinVerGreaterThanMaxVer() {
|
|||
}
|
||||
|
||||
func (suite *NotifierSuite) TestSMTPShouldWarnOnDisabledSTARTTLS() {
|
||||
suite.config.SMTP.Host = examplecom
|
||||
suite.config.SMTP.Host = exampleDotCom
|
||||
suite.config.SMTP.DisableStartTLS = true
|
||||
|
||||
ValidateNotifier(&suite.config, suite.validator)
|
||||
|
|
|
@ -35,18 +35,11 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru
|
|||
config.Inactivity = schema.DefaultSessionConfiguration.Inactivity // 5 min.
|
||||
}
|
||||
|
||||
if config.RememberMeDuration <= 0 && config.RememberMeDuration != schema.RememberMeDisabled {
|
||||
config.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration // 1 month.
|
||||
}
|
||||
|
||||
if config.Domain == "" {
|
||||
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))
|
||||
switch {
|
||||
case config.RememberMe == schema.RememberMeDisabled:
|
||||
config.DisableRememberMe = true
|
||||
case config.RememberMe <= 0:
|
||||
config.RememberMe = schema.DefaultSessionConfiguration.RememberMe // 1 month.
|
||||
}
|
||||
|
||||
if config.SameSite == "" {
|
||||
|
@ -54,6 +47,137 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru
|
|||
} else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) {
|
||||
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) {
|
||||
|
|
|
@ -3,7 +3,9 @@ package validator
|
|||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -14,7 +16,8 @@ import (
|
|||
func newDefaultSessionConfig() schema.SessionConfiguration {
|
||||
config := schema.SessionConfiguration{}
|
||||
config.Secret = testJWTSecret
|
||||
config.Domain = examplecom
|
||||
config.Domain = exampleDotCom
|
||||
config.Cookies = []schema.SessionCookieConfiguration{}
|
||||
|
||||
return config
|
||||
}
|
||||
|
@ -30,15 +33,144 @@ func TestShouldSetDefaultSessionValues(t *testing.T) {
|
|||
assert.Equal(t, schema.DefaultSessionConfiguration.Name, config.Name)
|
||||
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Expiration, config.Inactivity, config.RememberMeDuration = -1, -1, -2
|
||||
config.Expiration, config.Inactivity, config.RememberMe = -1, -1, -2
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
|
@ -46,7 +178,7 @@ func TestShouldSetDefaultSessionValuesWhenNegative(t *testing.T) {
|
|||
assert.Len(t, validator.Errors(), 0)
|
||||
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
||||
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) {
|
||||
|
@ -60,7 +192,7 @@ func TestShouldWarnSessionValuesWhenPotentiallyInvalid(t *testing.T) {
|
|||
require.Len(t, validator.Warnings(), 1)
|
||||
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) {
|
||||
|
@ -72,6 +204,8 @@ func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
|
|||
assert.Len(t, validator.Errors(), 0)
|
||||
validator.Clear()
|
||||
|
||||
config = newDefaultSessionConfig()
|
||||
|
||||
// Set redis config because password must be set only when redis is used.
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "redis.localhost",
|
||||
|
@ -81,8 +215,8 @@ func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
assert.Len(t, validator.Warnings(), 0)
|
||||
assert.Len(t, validator.Errors(), 0)
|
||||
|
||||
assert.Equal(t, 8, config.Redis.MaximumActiveConnections)
|
||||
}
|
||||
|
@ -98,7 +232,7 @@ func TestShouldRaiseErrorWithInvalidRedisPortLow(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
require.Len(t, validator.Warnings(), 0)
|
||||
require.Len(t, validator.Errors(), 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)
|
||||
validator.Clear()
|
||||
|
||||
config = newDefaultSessionConfig()
|
||||
config.Secret = ""
|
||||
|
||||
// Set redis config because password must be set only when redis is used.
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "redis.localhost",
|
||||
|
@ -153,6 +290,8 @@ func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
|
|||
assert.Len(t, validator.Errors(), 0)
|
||||
validator.Clear()
|
||||
|
||||
config = newDefaultSessionConfig()
|
||||
|
||||
// Set redis config because password must be set only when redis is used.
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "redis.localhost",
|
||||
|
@ -287,7 +426,24 @@ func TestShouldRaiseErrorsWhenRedisSentinelOptionsIncorrectlyConfigured(t *testi
|
|||
|
||||
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)
|
||||
|
||||
|
@ -434,6 +590,7 @@ func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
|
|||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
config.Domain = ""
|
||||
config.Cookies = []schema.SessionCookieConfiguration{}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
|
@ -449,9 +606,141 @@ func TestShouldRaiseErrorWhenDomainIsWildcard(t *testing.T) {
|
|||
|
||||
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.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) {
|
||||
|
@ -462,22 +751,28 @@ func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) {
|
|||
ValidateSession(&config, validator)
|
||||
|
||||
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()[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) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
var config schema.SessionConfiguration
|
||||
|
||||
validOptions := []string{"none", "lax", "strict"}
|
||||
|
||||
for _, opt := range validOptions {
|
||||
validator.Clear()
|
||||
|
||||
config = newDefaultSessionConfig()
|
||||
config.SameSite = opt
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.Len(t, validator.Warnings(), 0)
|
||||
assert.Len(t, validator.Errors(), 0)
|
||||
}
|
||||
}
|
||||
|
@ -487,7 +782,7 @@ func TestShouldSetDefaultWhenNegativeAndNotOverrideDisabledRememberMe(t *testing
|
|||
config := newDefaultSessionConfig()
|
||||
config.Inactivity = -1
|
||||
config.Expiration = -1
|
||||
config.RememberMeDuration = schema.RememberMeDisabled
|
||||
config.RememberMe = schema.RememberMeDisabled
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
|
@ -496,7 +791,8 @@ func TestShouldSetDefaultWhenNegativeAndNotOverrideDisabledRememberMe(t *testing
|
|||
|
||||
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
||||
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) {
|
||||
|
@ -505,7 +801,41 @@ func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
assert.Equal(t, config.RememberMeDuration, schema.DefaultSessionConfiguration.RememberMeDuration)
|
||||
assert.Len(t, validator.Warnings(), 0)
|
||||
assert.Len(t, validator.Errors(), 0)
|
||||
|
||||
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
|
||||
testRedirectionURL = "http://redirection.local"
|
||||
testUsername = "john"
|
||||
exampleDotCom = "example.com"
|
||||
)
|
||||
|
||||
// Duo constants.
|
||||
|
|
|
@ -2,9 +2,9 @@ package handlers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"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.
|
||||
|
@ -16,24 +16,23 @@ func CheckSafeRedirectionPOST(ctx *middlewares.AutheliaCtx) {
|
|||
return
|
||||
}
|
||||
|
||||
var reqBody checkURIWithinDomainRequestBody
|
||||
var (
|
||||
bodyJSON checkURIWithinDomainRequestBody
|
||||
targetURI *url.URL
|
||||
err error
|
||||
)
|
||||
|
||||
err := ctx.ParseBody(&reqBody)
|
||||
if err != nil {
|
||||
if err = ctx.ParseBody(&bodyJSON); err != nil {
|
||||
ctx.Error(fmt.Errorf("unable to parse request body: %w", err), messageOperationFailed)
|
||||
return
|
||||
}
|
||||
|
||||
safe, err := utils.IsURIStringSafeRedirection(reqBody.URI, ctx.Configuration.Session.Domain)
|
||||
if err != nil {
|
||||
ctx.Error(fmt.Errorf("unable to determine if uri %s is safe to redirect to: %w", reqBody.URI, err), messageOperationFailed)
|
||||
if targetURI, err = url.ParseRequestURI(bodyJSON.URI); 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)
|
||||
return
|
||||
}
|
||||
|
||||
err = ctx.SetJSONBody(checkURIWithinDomainResponseBody{
|
||||
OK: safe,
|
||||
})
|
||||
if err != nil {
|
||||
if err = ctx.SetJSONBody(checkURIWithinDomainResponseBody{OK: ctx.IsSafeRedirectionTargetURI(targetURI)}); err != nil {
|
||||
ctx.Error(fmt.Errorf("unable to create response body: %w", err), messageOperationFailed)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -4,64 +4,78 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
"github.com/authelia/authelia/v4/internal/mocks"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
)
|
||||
|
||||
var exampleDotComDomain = "example.com"
|
||||
func TestCheckSafeRedirection(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
userSession session.UserSession
|
||||
have string
|
||||
expected int
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
"ShouldReturnUnauthorized",
|
||||
session.UserSession{AuthenticationLevel: authentication.NotAuthenticated},
|
||||
"http://myapp.example.com",
|
||||
fasthttp.StatusUnauthorized,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"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_ForbiddenCall(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
||||
Username: "john",
|
||||
AuthenticationLevel: authentication.NotAuthenticated,
|
||||
})
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, tc.userSession)
|
||||
defer mock.Close()
|
||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
||||
|
||||
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
||||
URI: "http://myapp.example.com",
|
||||
URI: tc.have,
|
||||
})
|
||||
|
||||
CheckSafeRedirectionPOST(mock.Ctx)
|
||||
assert.Equal(t, 401, mock.Ctx.Response.StatusCode())
|
||||
}
|
||||
|
||||
func TestCheckSafeRedirection_UnsafeRedirection(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtxWithUserSession(t, session.UserSession{
|
||||
Username: "john",
|
||||
AuthenticationLevel: authentication.OneFactor,
|
||||
})
|
||||
defer mock.Close()
|
||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
||||
assert.Equal(t, tc.expected, mock.Ctx.Response.StatusCode())
|
||||
|
||||
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
||||
URI: "http://myapp.com",
|
||||
})
|
||||
|
||||
CheckSafeRedirectionPOST(mock.Ctx)
|
||||
if tc.expected == fasthttp.StatusOK {
|
||||
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) {
|
||||
|
@ -70,7 +84,7 @@ func TestShouldFailOnInvalidBody(t *testing.T) {
|
|||
AuthenticationLevel: authentication.OneFactor,
|
||||
})
|
||||
defer mock.Close()
|
||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
||||
mock.Ctx.Configuration.Session.Domain = exampleDotCom
|
||||
|
||||
mock.SetRequestBody(t, "not a valid json")
|
||||
|
||||
|
@ -84,7 +98,7 @@ func TestShouldFailOnInvalidURL(t *testing.T) {
|
|||
AuthenticationLevel: authentication.OneFactor,
|
||||
})
|
||||
defer mock.Close()
|
||||
mock.Ctx.Configuration.Session.Domain = exampleDotComDomain
|
||||
mock.Ctx.Configuration.Session.Domain = exampleDotCom
|
||||
|
||||
mock.SetRequestBody(t, checkURIWithinDomainRequestBody{
|
||||
URI: "https//invalid-url",
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/regulation"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
|
@ -72,7 +71,25 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
|
|||
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()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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.
|
||||
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.
|
||||
if keepMeLoggedIn {
|
||||
err = ctx.Providers.SessionProvider.UpdateExpiration(ctx.RequestCtx, ctx.Providers.SessionProvider.RememberMe)
|
||||
err = provider.UpdateExpiration(ctx.RequestCtx, provider.Config.RememberMe)
|
||||
if err != nil {
|
||||
ctx.Logger.Errorf(logFmtErrSessionSave, "updated expiration", regulation.AuthType1FA, bodyJSON.Username, err)
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"net/url"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
err = ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx)
|
||||
err = ctx.DestroySession()
|
||||
if err != nil {
|
||||
ctx.Error(fmt.Errorf("unable to destroy session during logout: %s", err), messageOperationFailed)
|
||||
}
|
||||
|
||||
redirectionURL, err := url.ParseRequestURI(body.TargetURL)
|
||||
if err == nil {
|
||||
responseBody.SafeTargetURL = utils.IsURISafeRedirection(redirectionURL, ctx.Configuration.Session.Domain)
|
||||
responseBody.SafeTargetURL = ctx.IsSafeRedirectionTargetURI(redirectionURL)
|
||||
}
|
||||
|
||||
if body.TargetURL != "" {
|
||||
|
|
|
@ -111,5 +111,9 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
|
|||
return
|
||||
}
|
||||
|
||||
if requester.GetResponseMode() == oidc.ResponseModeFormPost {
|
||||
ctx.SetUserValueBytes(middlewares.UserValueKeyFormPost, true)
|
||||
}
|
||||
|
||||
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) {
|
||||
userSession := ctx.GetSession()
|
||||
|
||||
err := ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
||||
err := ctx.RegenerateSession()
|
||||
if err != nil {
|
||||
ctx.Logger.Errorf(logFmtErrSessionRegenerate, regulation.AuthTypeDuo, userSession.Username, err)
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
"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/mocks"
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
|
@ -579,6 +580,18 @@ func (s *SecondFactorDuoPostSuite) TestShouldNotReturnRedirectURL() {
|
|||
func (s *SecondFactorDuoPostSuite) TestShouldRedirectUserToSafeTargetURL() {
|
||||
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().
|
||||
LoadPreferredDuoDevice(s.mock.Ctx, "john").
|
||||
Return(&model.DuoDevice{ID: 1, Username: "john", Device: "12345ABCDEFGHIJ67890", Method: "push"}, nil)
|
||||
|
|
|
@ -50,7 +50,7 @@ func TimeBasedOneTimePasswordPOST(ctx *middlewares.AutheliaCtx) {
|
|||
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)
|
||||
|
||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
"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/model"
|
||||
"github.com/authelia/authelia/v4/internal/regulation"
|
||||
|
@ -143,6 +144,18 @@ func (s *HandlerSignTOTPSuite) TestShouldNotReturnRedirectURL() {
|
|||
|
||||
func (s *HandlerSignTOTPSuite) TestShouldRedirectUserToSafeTargetURL() {
|
||||
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().
|
||||
LoadTOTPConfiguration(s.mock.Ctx, gomock.Any()).
|
||||
|
|
|
@ -44,7 +44,7 @@ func WebauthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
|||
extensions := map[string]any{}
|
||||
|
||||
if user.HasFIDOU2F() {
|
||||
extensions["appid"] = w.Config.RPOrigin
|
||||
extensions["appid"] = w.Config.RPOrigins[0]
|
||||
}
|
||||
|
||||
if len(extensions) != 0 {
|
||||
|
@ -171,7 +171,7 @@ func WebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
|||
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)
|
||||
|
||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||
|
|
|
@ -259,9 +259,11 @@ func TestUserInfoEndpoint_SetDefaultMethod(t *testing.T) {
|
|||
}
|
||||
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
sessionConfig := mock.Ctx.Configuration.Session
|
||||
|
||||
if resp.config != nil {
|
||||
mock.Ctx.Configuration = *resp.config
|
||||
mock.Ctx.Configuration.Session = sessionConfig
|
||||
}
|
||||
|
||||
// Set the initial user session.
|
||||
|
|
|
@ -19,14 +19,6 @@ import (
|
|||
"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 {
|
||||
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.
|
||||
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 {
|
||||
hasSubject, level := authorizer.GetRequiredLevel(
|
||||
authorization.Subject{
|
||||
|
@ -62,7 +54,7 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
|
|||
Groups: userGroups,
|
||||
IP: clientIP,
|
||||
},
|
||||
authorization.NewObjectRaw(&targetURL, method))
|
||||
authorization.NewObjectRaw(targetURL, method))
|
||||
|
||||
switch {
|
||||
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) {
|
||||
if userSession.KeepMeLoggedIn || isUserAnonymous || int64(ctx.Providers.SessionProvider.Inactivity.Seconds()) == 0 {
|
||||
domainSession, err := ctx.GetSessionProvider()
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
@ -151,7 +148,7 @@ func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userS
|
|||
|
||||
if isSessionInactiveTooLong(ctx, userSession, isUserAnonymous) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
@ -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 == 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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
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 (
|
||||
statusCode int
|
||||
friendlyUsername string
|
||||
|
@ -211,8 +208,8 @@ func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, is
|
|||
redirectionURL := ctxGetPortalURL(ctx)
|
||||
|
||||
if redirectionURL != nil {
|
||||
if !utils.IsURISafeRedirection(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, 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, cookieDomain)
|
||||
|
||||
ctx.ReplyUnauthorized()
|
||||
|
||||
|
@ -414,6 +411,7 @@ func verifyAuth(ctx *middlewares.AutheliaCtx, targetURL *url.URL, refreshProfile
|
|||
}
|
||||
|
||||
userSession := ctx.GetSession()
|
||||
|
||||
if username, name, groups, emails, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession, refreshProfile, refreshProfileInterval); err != nil {
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -447,7 +445,7 @@ func VerifyGET(cfg schema.AuthenticationBackend) middlewares.RequestHandler {
|
|||
return
|
||||
}
|
||||
|
||||
if !isSchemeHTTPS(targetURL) && !isSchemeWSS(targetURL) {
|
||||
if !utils.IsURISecure(targetURL) {
|
||||
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())
|
||||
ctx.ReplyUnauthorized()
|
||||
|
@ -455,14 +453,32 @@ func VerifyGET(cfg schema.AuthenticationBackend) middlewares.RequestHandler {
|
|||
return
|
||||
}
|
||||
|
||||
if !isURLUnderProtectedDomain(targetURL, ctx.Configuration.Session.Domain) {
|
||||
ctx.Logger.Errorf("Target URL %s is not under the protected domain %s",
|
||||
targetURL.String(), ctx.Configuration.Session.Domain)
|
||||
cookieDomain := ctx.GetTargetURICookieDomain(targetURL)
|
||||
|
||||
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()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Logger.Debugf("Target URL '%s' was detected as a match to the '%s' session cookie domain", targetURL.String(), cookieDomain)
|
||||
|
||||
method := ctx.XForwardedMethod()
|
||||
isBasicAuth, username, name, groups, emails, authLevel, err := verifyAuth(ctx, targetURL, refreshProfile, refreshProfileInterval)
|
||||
|
||||
|
@ -474,12 +490,12 @@ func VerifyGET(cfg schema.AuthenticationBackend) middlewares.RequestHandler {
|
|||
return
|
||||
}
|
||||
|
||||
handleUnauthorized(ctx, targetURL, isBasicAuth, username, method)
|
||||
handleUnauthorized(ctx, targetURL, cookieDomain, isBasicAuth, username, method)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
authorized := isTargetURLAuthorized(ctx.Providers.Authorizer, *targetURL, username,
|
||||
authorized := isTargetURLAuthorized(ctx.Providers.Authorizer, targetURL, username,
|
||||
groups, ctx.RemoteIP(), method, authLevel)
|
||||
|
||||
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.ReplyForbidden()
|
||||
case NotAuthorized:
|
||||
handleUnauthorized(ctx, targetURL, isBasicAuth, username, method)
|
||||
handleUnauthorized(ctx, targetURL, cookieDomain, isBasicAuth, username, method)
|
||||
case Authorized:
|
||||
setForwardedHeaders(&ctx.Response.Header, username, name, groups, emails)
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ func TestShouldRaiseWhenTargetUrlIsMalformed(t *testing.T) {
|
|||
|
||||
func TestShouldRaiseWhenNoHeaderProvidedToDetectTargetURL(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
mock.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||
|
||||
defer mock.Close()
|
||||
_, err := mock.Ctx.GetOriginalURL()
|
||||
assert.Error(t, err)
|
||||
|
@ -53,6 +55,7 @@ func TestShouldRaiseWhenNoXForwardedHostHeaderProvidedToDetectTargetURL(t *testi
|
|||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
|
||||
mock.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
||||
_, err := mock.Ctx.GetOriginalURL()
|
||||
assert.Error(t, err)
|
||||
|
@ -162,7 +165,7 @@ func TestShouldCheckAuthorizationMatching(t *testing.T) {
|
|||
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",
|
||||
rule.Policy, rule.AuthLevel, rule.ExpectedMatching, matching)
|
||||
}
|
||||
|
@ -510,7 +513,6 @@ func TestShouldNotCrashOnEmptyEmail(t *testing.T) {
|
|||
userSession.AuthenticationLevel = authentication.OneFactor
|
||||
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
||||
|
||||
fmt.Printf("Time is %v\n", userSession.RefreshTTL)
|
||||
err := mock.Ctx.SaveSession(userSession)
|
||||
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},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testCase.String(), func(t *testing.T) {
|
||||
for i, tc := range testCases {
|
||||
t.Run(tc.String(), func(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
|
||||
mock.Clock.Set(time.Now())
|
||||
|
||||
mock.Ctx.Request.Header.Set("X-Original-URL", tc.URL)
|
||||
|
||||
userSession := mock.Ctx.GetSession()
|
||||
userSession.Username = testCase.Username
|
||||
userSession.Emails = testCase.Emails
|
||||
userSession.AuthenticationLevel = testCase.AuthenticationLevel
|
||||
userSession.Username = tc.Username
|
||||
userSession.Emails = tc.Emails
|
||||
userSession.AuthenticationLevel = tc.AuthenticationLevel
|
||||
userSession.RefreshTTL = mock.Clock.Now().Add(5 * time.Minute)
|
||||
|
||||
err := mock.Ctx.SaveSession(userSession)
|
||||
require.NoError(t, err)
|
||||
|
||||
mock.Ctx.Request.Header.Set("X-Original-URL", testCase.URL)
|
||||
|
||||
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",
|
||||
testCase.URL, testCase.AuthenticationLevel, actualStatus, expStatus)
|
||||
tc.URL, tc.AuthenticationLevel, actualStatus, expStatus)
|
||||
|
||||
if testCase.ExpectedStatusCode == 200 && testCase.Username != "" {
|
||||
assert.Equal(t, []byte(testCase.Username), mock.Ctx.Response.Header.Peek("Remote-User"))
|
||||
fmt.Println(i)
|
||||
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"))
|
||||
} else {
|
||||
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())
|
||||
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.
|
||||
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.Username = testUsername
|
||||
|
@ -719,8 +724,6 @@ func TestShouldDestroySessionWhenInactiveForTooLong(t *testing.T) {
|
|||
err := mock.Ctx.SaveSession(userSession)
|
||||
require.NoError(t, err)
|
||||
|
||||
mock.Ctx.Request.Header.Set("X-Original-URL", "https://two-factor.example.com")
|
||||
|
||||
VerifyGET(verifyGetCfg)(mock.Ctx)
|
||||
|
||||
// The session has been destroyed.
|
||||
|
@ -739,10 +742,10 @@ func TestShouldDestroySessionWhenInactiveForTooLongUsingDurationNotation(t *test
|
|||
clock := utils.TestingClock{}
|
||||
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.
|
||||
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.Username = testUsername
|
||||
|
@ -768,7 +771,7 @@ func TestShouldKeepSessionWhenUserCheckedRememberMeAndIsInactiveForTooLong(t *te
|
|||
|
||||
mock.Clock.Set(time.Now())
|
||||
|
||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
||||
mock.Ctx.Configuration.Session.Cookies[0].Inactivity = testInactivity
|
||||
|
||||
userSession := mock.Ctx.GetSession()
|
||||
userSession.Username = testUsername
|
||||
|
@ -800,7 +803,7 @@ func TestShouldKeepSessionWhenInactivityTimeoutHasNotBeenExceeded(t *testing.T)
|
|||
|
||||
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)
|
||||
|
||||
|
@ -836,10 +839,10 @@ func TestShouldRedirectWhenSessionInactiveForTooLongAndRDParamProvided(t *testin
|
|||
clock := utils.TestingClock{}
|
||||
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.
|
||||
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)
|
||||
|
||||
|
@ -899,7 +902,7 @@ func TestShouldUpdateInactivityTimestampEvenWhenHittingForbiddenResources(t *tes
|
|||
|
||||
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)
|
||||
|
||||
|
@ -974,47 +977,6 @@ func TestShouldURLEncodeRedirectionHeader(t *testing.T) {
|
|||
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) {
|
||||
GetURL := func(u string) *url.URL {
|
||||
x, err := url.ParseRequestURI(u)
|
||||
|
@ -1435,10 +1397,10 @@ func TestShouldNotRedirectRequestsForBypassACLWhenInactiveForTooLong(t *testing.
|
|||
clock.Set(time.Now())
|
||||
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.
|
||||
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.Username = testUsername
|
||||
|
@ -1527,7 +1489,7 @@ func TestIsSessionInactiveTooLong(t *testing.T) {
|
|||
|
||||
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.Clock.Set(tc.now)
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
// Handle1FAResponse handle the redirection upon 1FA authentication.
|
||||
|
@ -57,7 +56,7 @@ func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI, requestMethod st
|
|||
return
|
||||
}
|
||||
|
||||
if !utils.IsURISafeRedirection(targetURL, ctx.Configuration.Session.Domain) {
|
||||
if !ctx.IsSafeRedirectionTargetURI(targetURL) {
|
||||
ctx.Logger.Debugf("Redirection URL %s is not safe", targetURI)
|
||||
|
||||
if !ctx.Providers.Authorizer.IsSecondFactorEnabled() && ctx.Configuration.DefaultRedirectionURL != "" {
|
||||
|
@ -98,14 +97,18 @@ func Handle2FAResponse(ctx *middlewares.AutheliaCtx, targetURI string) {
|
|||
return
|
||||
}
|
||||
|
||||
var safe bool
|
||||
|
||||
if safe, err = utils.IsURIStringSafeRedirection(targetURI, ctx.Configuration.Session.Domain); err != nil {
|
||||
ctx.Error(fmt.Errorf("unable to check target URL: %s", err), messageMFAValidationFailed)
|
||||
var (
|
||||
parsedURI *url.URL
|
||||
safe bool
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
safe = ctx.IsSafeRedirectionTargetURI(parsedURI)
|
||||
|
||||
if safe {
|
||||
ctx.Logger.Debugf("Redirection URL %s is safe", targetURI)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package handlers
|
|||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
|
@ -44,7 +45,7 @@ func newWebauthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error)
|
|||
config := &webauthn.Config{
|
||||
RPDisplayName: ctx.Configuration.Webauthn.DisplayName,
|
||||
RPID: rpID,
|
||||
RPOrigin: origin,
|
||||
RPOrigins: []string{origin},
|
||||
RPIcon: "",
|
||||
|
||||
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()),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -146,6 +146,7 @@ func TestWebauthnGetUserWithErr(t *testing.T) {
|
|||
|
||||
func TestWebauthnNewWebauthnShouldReturnErrWhenHeadersNotAvailable(t *testing.T) {
|
||||
ctx := mocks.NewMockAutheliaCtx(t)
|
||||
ctx.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||
|
||||
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.
|
||||
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 {
|
||||
ctx.Logger.Error("Unable to retrieve user session")
|
||||
return session.NewDefaultUserSession()
|
||||
|
@ -251,7 +305,32 @@ func (ctx *AutheliaCtx) GetSession() session.UserSession {
|
|||
|
||||
// SaveSession save the content of the session.
|
||||
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.
|
||||
|
|
|
@ -182,6 +182,8 @@ func TestShouldGetOriginalURLFromForwardedHeadersWithURI(t *testing.T) {
|
|||
|
||||
func TestShouldFallbackToNonXForwardedHeaders(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
mock.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||
|
||||
defer mock.Close()
|
||||
|
||||
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
||||
|
@ -196,6 +198,8 @@ func TestShouldOnlyFallbackToNonXForwardedHeadersWhenNil(t *testing.T) {
|
|||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
|
||||
mock.Ctx.Request.Header.Del("X-Forwarded-Host")
|
||||
|
||||
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
||||
mock.Ctx.RequestCtx.Request.SetHost("localhost")
|
||||
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "auth.example.com:1234")
|
||||
|
|
|
@ -54,7 +54,8 @@ var (
|
|||
headerValueVaryWildcard = []byte("Accept-Encoding")
|
||||
headerValueOriginWildcard = []byte("*")
|
||||
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")
|
||||
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 = []byte("base_url")
|
||||
|
||||
// UserValueKeyFormPost is the User Value key where we indicate the form_post response mode.
|
||||
UserValueKeyFormPost = []byte("form_post")
|
||||
|
||||
headerSeparator = []byte(", ")
|
||||
|
||||
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.
|
||||
func SecurityHeadersNoStore(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
|
|
|
@ -51,9 +51,25 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
|||
mockAuthelia.Clock.Set(datetime)
|
||||
|
||||
config := schema.Configuration{}
|
||||
config.Session.RememberMeDuration = schema.DefaultSessionConfiguration.RememberMeDuration
|
||||
config.Session.Name = "authelia_session"
|
||||
config.Session.Domain = "example.com"
|
||||
config.Session.Cookies = []schema.SessionCookieConfiguration{
|
||||
{
|
||||
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.Rules = []schema.ACLRule{{
|
||||
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.
|
||||
// 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)
|
||||
mockAuthelia.Ctx = ctx
|
||||
|
||||
|
|
|
@ -213,7 +213,7 @@ func (d *WebauthnDevice) UpdateSignInInfo(config *webauthn.Config, now time.Time
|
|||
|
||||
switch d.AttestationType {
|
||||
case attestationTypeFIDOU2F:
|
||||
d.RPID = config.RPOrigin
|
||||
d.RPID = config.RPOrigins[0]
|
||||
default:
|
||||
d.RPID = config.RPID
|
||||
}
|
||||
|
|
|
@ -139,8 +139,6 @@ func (n *SMTPNotifier) Send(ctx context.Context, recipient mail.Address, subject
|
|||
|
||||
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 {
|
||||
return fmt.Errorf("notifier: smtp: failed to establish client: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,27 @@
|
|||
package ntp
|
||||
|
||||
const (
|
||||
ntpClientModeValue uint8 = 3 // 00000011.
|
||||
ntpLeapEnabledValue uint8 = 64 // 01000000.
|
||||
ntpVersion3Value uint8 = 24 // 00011000.
|
||||
ntpVersion4Value uint8 = 40 // 00101000.
|
||||
)
|
||||
|
||||
const ntpEpochOffset = 2208988800
|
||||
|
||||
const (
|
||||
ntpV3 ntpVersion = iota
|
||||
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
|
||||
}
|
||||
|
||||
req := &ntpPacket{LeapVersionMode: ntpLeapVersionClientMode(false, version)}
|
||||
req := &ntpPacket{LeapVersionMode: ntpLeapVersionClientMode(version)}
|
||||
|
||||
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)
|
||||
|
|
|
@ -3,20 +3,18 @@ package ntp
|
|||
import "time"
|
||||
|
||||
// ntpLeapVersionClientMode does the mathematics to configure the leap/version/mode value of an NTP client packet.
|
||||
func ntpLeapVersionClientMode(leap bool, version ntpVersion) (lvm uint8) {
|
||||
lvm = ntpClientModeValue
|
||||
|
||||
if leap {
|
||||
lvm += ntpLeapEnabledValue
|
||||
}
|
||||
func ntpLeapVersionClientMode(version ntpVersion) (lvm uint8) {
|
||||
lvm = (lvm & maskMode) | uint8(modeClient)
|
||||
|
||||
switch version {
|
||||
case ntpV3:
|
||||
lvm += ntpVersion3Value
|
||||
lvm = (lvm & maskVersion) | uint8(version3)<<3
|
||||
case ntpV4:
|
||||
lvm += ntpVersion4Value
|
||||
lvm = (lvm & maskVersion) | uint8(version4)<<3
|
||||
}
|
||||
|
||||
lvm = (lvm & maskLeap) | uint8(leapUnknown)<<6
|
||||
|
||||
return lvm
|
||||
}
|
||||
|
||||
|
|
|
@ -29,13 +29,9 @@ func TestNtpPacketToTime(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLeapVersionClientMode(t *testing.T) {
|
||||
v3Noleap := uint8(27)
|
||||
v4Noleap := uint8(43)
|
||||
v3leap := uint8(91)
|
||||
v4leap := uint8(107)
|
||||
v3Noleap := uint8(0xdb)
|
||||
v4Noleap := uint8(0xe3)
|
||||
|
||||
assert.Equal(t, v3Noleap, ntpLeapVersionClientMode(false, ntpV3))
|
||||
assert.Equal(t, v4Noleap, ntpLeapVersionClientMode(false, ntpV4))
|
||||
assert.Equal(t, v3leap, ntpLeapVersionClientMode(true, ntpV3))
|
||||
assert.Equal(t, v4leap, ntpLeapVersionClientMode(true, ntpV4))
|
||||
assert.Equal(t, v3Noleap, ntpLeapVersionClientMode(ntpV3))
|
||||
assert.Equal(t, v4Noleap, ntpLeapVersionClientMode(ntpV4))
|
||||
}
|
||||
|
|
|
@ -19,10 +19,11 @@ import (
|
|||
"github.com/ory/fosite/token/jwt"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/templates"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
func NewConfig(config *schema.OpenIDConnectConfiguration) *Config {
|
||||
func NewConfig(config *schema.OpenIDConnectConfiguration, templates *templates.Provider) *Config {
|
||||
c := &Config{
|
||||
GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)),
|
||||
SendDebugMessagesToClients: config.EnableClientDebugMessages,
|
||||
|
@ -38,6 +39,7 @@ func NewConfig(config *schema.OpenIDConnectConfiguration) *Config {
|
|||
EnforcePublicClients: config.EnforcePKCE != "never",
|
||||
AllowPlainChallengeMethod: config.EnablePKCEPlainChallenge,
|
||||
},
|
||||
Templates: templates,
|
||||
}
|
||||
|
||||
c.Strategy.Core = &HMACCoreStrategy{
|
||||
|
@ -85,6 +87,8 @@ type Config struct {
|
|||
HTTPClient *retryablehttp.Client
|
||||
FormPostHTMLTemplate *template.Template
|
||||
MessageCatalog i18n.MessageCatalog
|
||||
|
||||
Templates *templates.Provider
|
||||
}
|
||||
|
||||
type HashConfig struct {
|
||||
|
@ -502,7 +506,11 @@ func (c *Config) GetMessageCatalog(ctx context.Context) (catalog i18n.MessageCat
|
|||
|
||||
// GetFormPostHTMLTemplate returns the form post HTML 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.
|
||||
|
|
|
@ -9,10 +9,11 @@ import (
|
|||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/storage"
|
||||
"github.com/authelia/authelia/v4/internal/templates"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -20,7 +21,7 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s
|
|||
provider = &OpenIDConnectProvider{
|
||||
JSONWriter: herodot.NewJSONWriter(nil),
|
||||
Store: NewStore(config, store),
|
||||
Config: NewConfig(config),
|
||||
Config: NewConfig(config, templates),
|
||||
}
|
||||
|
||||
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-----"
|
||||
|
||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) {
|
||||
provider, err := NewOpenIDConnectProvider(nil, nil)
|
||||
provider, err := NewOpenIDConnectProvider(nil, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, provider)
|
||||
|
@ -39,7 +39,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
|||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
}, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -88,7 +88,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
|||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
}, nil, nil)
|
||||
|
||||
assert.NotNil(t, provider)
|
||||
assert.NoError(t, err)
|
||||
|
@ -109,7 +109,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
}, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -199,7 +199,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
|||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
}, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -278,7 +278,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
}, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
|
|
@ -236,27 +236,27 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
|||
}
|
||||
|
||||
if providers.OpenIDConnect != nil {
|
||||
middlewareOIDC := middlewares.NewBridgeBuilder(config, providers).WithPreMiddlewares(
|
||||
middlewares.SecurityHeaders, middlewares.SecurityHeadersCSPNone, middlewares.SecurityHeadersNoStore,
|
||||
bridgeOIDC := middlewares.NewBridgeBuilder(config, providers).WithPreMiddlewares(
|
||||
middlewares.SecurityHeaders, middlewares.SecurityHeadersCSPNoneOpenIDConnect, middlewares.SecurityHeadersNoStore,
|
||||
).Build()
|
||||
|
||||
r.GET("/api/oidc/consent", middlewareOIDC(handlers.OpenIDConnectConsentGET))
|
||||
r.POST("/api/oidc/consent", middlewareOIDC(handlers.OpenIDConnectConsentPOST))
|
||||
r.GET("/api/oidc/consent", bridgeOIDC(handlers.OpenIDConnectConsentGET))
|
||||
r.POST("/api/oidc/consent", bridgeOIDC(handlers.OpenIDConnectConsentPOST))
|
||||
|
||||
allowedOrigins := utils.StringSliceFromURLs(config.IdentityProviders.OIDC.CORS.AllowedOrigins)
|
||||
|
||||
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.GET(oidc.EndpointPathWellKnownOAuthAuthorizationServer, policyCORSPublicGET.Middleware(middlewareOIDC(handlers.OAuthAuthorizationServerWellKnownGET)))
|
||||
r.GET(oidc.EndpointPathWellKnownOAuthAuthorizationServer, policyCORSPublicGET.Middleware(bridgeOIDC(handlers.OAuthAuthorizationServerWellKnownGET)))
|
||||
|
||||
r.OPTIONS(oidc.EndpointPathJWKs, policyCORSPublicGET.HandleOPTIONS)
|
||||
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.
|
||||
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().
|
||||
WithAllowedMethods(fasthttp.MethodOptions, fasthttp.MethodGet, fasthttp.MethodPost).
|
||||
|
@ -265,13 +265,13 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
|||
Build()
|
||||
|
||||
r.OPTIONS(oidc.EndpointPathAuthorization, policyCORSAuthorization.HandleOnlyOPTIONS)
|
||||
r.GET(oidc.EndpointPathAuthorization, policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
||||
r.POST(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(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
||||
|
||||
// TODO (james-d-elliott): Remove in GA. This is a legacy endpoint.
|
||||
r.OPTIONS("/api/oidc/authorize", policyCORSAuthorization.HandleOnlyOPTIONS)
|
||||
r.GET("/api/oidc/authorize", policyCORSAuthorization.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
||||
r.POST("/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(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectAuthorization))))
|
||||
|
||||
policyCORSToken := middlewares.NewCORSPolicyBuilder().
|
||||
WithAllowCredentials(true).
|
||||
|
@ -281,7 +281,7 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
|||
Build()
|
||||
|
||||
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().
|
||||
WithAllowCredentials(true).
|
||||
|
@ -291,8 +291,8 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
|||
Build()
|
||||
|
||||
r.OPTIONS(oidc.EndpointPathUserinfo, policyCORSUserinfo.HandleOPTIONS)
|
||||
r.GET(oidc.EndpointPathUserinfo, policyCORSUserinfo.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
|
||||
r.POST(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(bridgeOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OpenIDConnectUserinfo))))
|
||||
|
||||
policyCORSIntrospection := middlewares.NewCORSPolicyBuilder().
|
||||
WithAllowCredentials(true).
|
||||
|
@ -302,11 +302,11 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
|||
Build()
|
||||
|
||||
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.
|
||||
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().
|
||||
WithAllowCredentials(true).
|
||||
|
@ -316,11 +316,11 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
|
|||
Build()
|
||||
|
||||
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.
|
||||
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
|
||||
|
|
|
@ -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/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/random"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
"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))
|
||||
}
|
||||
|
||||
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.Logger.WithError(err).Errorf("Error occcurred rendering template")
|
||||
|
||||
|
@ -190,7 +200,7 @@ func NewTemplatedFileOptions(config *schema.Configuration) (opts *TemplatedFileO
|
|||
opts = &TemplatedFileOptions{
|
||||
AssetPath: config.Server.AssetPath,
|
||||
DuoSelfEnrollment: strFalse,
|
||||
RememberMe: strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled),
|
||||
RememberMe: strconv.FormatBool(!config.Session.DisableRememberMe),
|
||||
ResetPassword: strconv.FormatBool(!config.AuthenticationBackend.PasswordReset.Disable),
|
||||
ResetPasswordCustomURL: config.AuthenticationBackend.PasswordReset.CustomURL.String(),
|
||||
Theme: config.Theme,
|
||||
|
@ -227,7 +237,11 @@ type TemplatedFileOptions struct {
|
|||
}
|
||||
|
||||
// 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{
|
||||
Base: base,
|
||||
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.
|
||||
func (options *TemplatedFileOptions) OpenAPIData(base, baseURL, nonce string) TemplatedFileOpenAPIData {
|
||||
return TemplatedFileOpenAPIData{
|
||||
|
|
|
@ -9,6 +9,12 @@ import (
|
|||
"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.
|
||||
type EncryptingSerializer struct {
|
||||
key [32]byte
|
||||
|
@ -21,8 +27,8 @@ func NewEncryptingSerializer(secret string) *EncryptingSerializer {
|
|||
}
|
||||
|
||||
// Encode encode and encrypt session.
|
||||
func (e *EncryptingSerializer) Encode(src session.Dict) ([]byte, error) {
|
||||
if len(src.D) == 0 {
|
||||
func (e *EncryptingSerializer) Encode(src session.Dict) (data []byte, err error) {
|
||||
if len(src.KV) == 0 {
|
||||
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)
|
||||
}
|
||||
|
||||
encryptedDst, err := utils.Encrypt(dst, &e.key)
|
||||
if err != nil {
|
||||
if data, err = utils.Encrypt(dst, &e.key); err != nil {
|
||||
return nil, fmt.Errorf("unable to encrypt session: %v", err)
|
||||
}
|
||||
|
||||
return encryptedDst, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dst.Reset()
|
||||
for k := range dst.KV {
|
||||
delete(dst.KV, k)
|
||||
}
|
||||
|
||||
decryptedSrc, err := utils.Decrypt(src, &e.key)
|
||||
if err != nil {
|
||||
var data []byte
|
||||
|
||||
if data, err = utils.Decrypt(src, &e.key); err != nil {
|
||||
return fmt.Errorf("unable to decrypt session: %s", err)
|
||||
}
|
||||
|
||||
_, err = dst.UnmarshalMsg(decryptedSrc)
|
||||
_, err = dst.UnmarshalMsg(data)
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,8 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func TestShouldEncryptAndDecrypt(t *testing.T) {
|
||||
payload := session.Dict{}
|
||||
payload.Set("key", "value")
|
||||
payload := session.Dict{KV: map[string]interface{}{"key": "value"}}
|
||||
|
||||
dst, err := payload.MarshalMsg(nil)
|
||||
require.NoError(t, err)
|
||||
|
@ -25,12 +24,11 @@ func TestShouldEncryptAndDecrypt(t *testing.T) {
|
|||
err = serializer.Decode(&decodedPayload, encryptedDst)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "value", decodedPayload.Get("key"))
|
||||
assert.Equal(t, "value", decodedPayload.KV["key"])
|
||||
}
|
||||
|
||||
func TestShouldNotSupportUnencryptedSessionForBackwardCompatibility(t *testing.T) {
|
||||
payload := session.Dict{}
|
||||
payload.Set("key", "value")
|
||||
payload := session.Dict{KV: map[string]interface{}{"key": "value"}}
|
||||
|
||||
dst, err := payload.MarshalMsg(nil)
|
||||
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 (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"time"
|
||||
"fmt"
|
||||
|
||||
fasthttpsession "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/fasthttp/session/v2"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/logging"
|
||||
)
|
||||
|
||||
// Provider a session provider.
|
||||
// Provider contains a list of domain sessions.
|
||||
type Provider struct {
|
||||
sessionHolder *fasthttpsession.Session
|
||||
RememberMe time.Duration
|
||||
Inactivity time.Duration
|
||||
sessions map[string]*Session
|
||||
}
|
||||
|
||||
// NewProvider instantiate a session provider given a configuration.
|
||||
func NewProvider(config schema.SessionConfiguration, certPool *x509.CertPool) *Provider {
|
||||
c := NewProviderConfig(config, certPool)
|
||||
log := logging.Logger()
|
||||
|
||||
provider := new(Provider)
|
||||
provider.sessionHolder = fasthttpsession.New(c.config)
|
||||
name, p, s, err := NewSessionProvider(config, certPool)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
logger := logging.Logger()
|
||||
|
||||
provider.Inactivity, provider.RememberMe = config.Inactivity, config.RememberMeDuration
|
||||
provider := &Provider{
|
||||
sessions: map[string]*Session{},
|
||||
}
|
||||
|
||||
var (
|
||||
providerImpl fasthttpsession.Provider
|
||||
err error
|
||||
holder *session.Session
|
||||
)
|
||||
|
||||
switch {
|
||||
case c.redisConfig != nil:
|
||||
providerImpl, err = redis.New(*c.redisConfig)
|
||||
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)
|
||||
}
|
||||
for _, dconfig := range config.Cookies {
|
||||
if _, holder, err = NewProviderConfigAndSession(dconfig, name, s, p); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = provider.sessionHolder.SetProvider(providerImpl)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
provider.sessions[dconfig.Domain] = &Session{
|
||||
Config: dconfig,
|
||||
sessionHolder: holder,
|
||||
}
|
||||
}
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
// GetSession return the user session from a request.
|
||||
func (p *Provider) GetSession(ctx *fasthttp.RequestCtx) (UserSession, error) {
|
||||
store, err := p.sessionHolder.Get(ctx)
|
||||
|
||||
if err != nil {
|
||||
return NewDefaultUserSession(), err
|
||||
// Get returns session information for specified domain.
|
||||
func (p *Provider) Get(domain string) (*Session, error) {
|
||||
if domain == "" {
|
||||
return nil, fmt.Errorf("can not get session from an undefined domain")
|
||||
}
|
||||
|
||||
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
|
||||
// and save it in the store.
|
||||
if !ok {
|
||||
userSession := NewDefaultUserSession()
|
||||
|
||||
store.Set(userSessionStorerKey, userSession)
|
||||
|
||||
return userSession, nil
|
||||
if !found {
|
||||
return nil, fmt.Errorf("no session found for domain '%s'", domain)
|
||||
}
|
||||
|
||||
var userSession UserSession
|
||||
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
|
||||
return s, nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/fasthttp/session/v2"
|
||||
"github.com/fasthttp/session/v2/providers/memory"
|
||||
"github.com/fasthttp/session/v2/providers/redis"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
@ -18,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
// 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.SessionIDGeneratorFunc = func() []byte {
|
||||
|
@ -61,16 +62,42 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
|
|||
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.
|
||||
switch {
|
||||
case config.Redis != nil:
|
||||
serializer := NewEncryptingSerializer(config.Secret)
|
||||
serializer = NewEncryptingSerializer(config.Secret)
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
|
||||
|
@ -92,8 +119,9 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
|
|||
}
|
||||
}
|
||||
|
||||
providerName = "redis-sentinel"
|
||||
redisSentinelConfig = &redis.FailoverConfig{
|
||||
name = "redis-sentinel"
|
||||
|
||||
provider, err = redis.NewFailoverCluster(redis.FailoverConfig{
|
||||
Logger: logging.LoggerCtxPrintf(logrus.TraceLevel),
|
||||
MasterName: config.Redis.HighAvailability.SentinelName,
|
||||
SentinelAddrs: addrs,
|
||||
|
@ -109,9 +137,9 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
|
|||
IdleTimeout: 300,
|
||||
TLSConfig: tlsConfig,
|
||||
KeyPrefix: "authelia-session",
|
||||
}
|
||||
})
|
||||
} else {
|
||||
providerName = "redis"
|
||||
name = "redis"
|
||||
network := "tcp"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
redisConfig = &redis.Config{
|
||||
provider, err = redis.New(redis.Config{
|
||||
Logger: logging.LoggerCtxPrintf(logrus.TraceLevel),
|
||||
Network: network,
|
||||
Addr: addr,
|
||||
|
@ -135,19 +163,12 @@ func NewProviderConfig(config schema.SessionConfiguration, certPool *x509.CertPo
|
|||
IdleTimeout: 300,
|
||||
TLSConfig: tlsConfig,
|
||||
KeyPrefix: "authelia-session",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
c.EncodeFunc = serializer.Encode
|
||||
c.DecodeFunc = serializer.Decode
|
||||
default:
|
||||
providerName = "memory"
|
||||
name = "memory"
|
||||
provider, err = memory.New(memory.Config{})
|
||||
}
|
||||
|
||||
return ProviderConfig{
|
||||
c,
|
||||
redisConfig,
|
||||
redisSentinelConfig,
|
||||
providerName,
|
||||
}
|
||||
return name, provider, serializer, err
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
|
@ -14,16 +13,31 @@ import (
|
|||
"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) {
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, NewDefaultUserSession(), session)
|
||||
}
|
||||
|
@ -31,22 +45,19 @@ func TestShouldInitializerSession(t *testing.T) {
|
|||
func TestShouldUpdateSession(t *testing.T) {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
|
||||
configuration := schema.SessionConfiguration{}
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
provider, err := newTestSession()
|
||||
assert.NoError(t, err)
|
||||
|
||||
provider := NewProvider(configuration, nil)
|
||||
session, _ := provider.GetSession(ctx)
|
||||
|
||||
session.Username = testUsername
|
||||
session.AuthenticationLevel = authentication.TwoFactor
|
||||
|
||||
err := provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
err = provider.SaveSession(ctx, session)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, UserSession{
|
||||
Username: testUsername,
|
||||
|
@ -56,26 +67,23 @@ func TestShouldUpdateSession(t *testing.T) {
|
|||
|
||||
func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
configuration := schema.SessionConfiguration{}
|
||||
|
||||
timeOneFactor := time.Unix(1625048140, 0)
|
||||
timeTwoFactor := time.Unix(1625048150, 0)
|
||||
timeZeroFactor := time.Unix(0, 0)
|
||||
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
provider, err := newTestSession()
|
||||
assert.NoError(t, err)
|
||||
|
||||
provider := NewProvider(configuration, nil)
|
||||
session, _ := provider.GetSession(ctx)
|
||||
|
||||
session.SetOneFactor(timeOneFactor, &authentication.UserDetails{Username: testUsername}, false)
|
||||
|
||||
err := provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
err = provider.SaveSession(ctx, session)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
authAt, err := session.AuthenticatedTime(authorization.OneFactor)
|
||||
assert.NoError(t, err)
|
||||
|
@ -100,10 +108,10 @@ func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
|||
session.SetTwoFactorDuo(timeTwoFactor)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, UserSession{
|
||||
Username: testUsername,
|
||||
|
@ -129,26 +137,23 @@ func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
|||
|
||||
func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
configuration := schema.SessionConfiguration{}
|
||||
|
||||
timeOneFactor := time.Unix(1625048140, 0)
|
||||
timeTwoFactor := time.Unix(1625048150, 0)
|
||||
timeZeroFactor := time.Unix(0, 0)
|
||||
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
provider, err := newTestSession()
|
||||
assert.NoError(t, err)
|
||||
|
||||
provider := NewProvider(configuration, nil)
|
||||
session, _ := provider.GetSession(ctx)
|
||||
|
||||
session.SetOneFactor(timeOneFactor, &authentication.UserDetails{Username: testUsername}, false)
|
||||
|
||||
err := provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
err = provider.SaveSession(ctx, session)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
authAt, err := session.AuthenticatedTime(authorization.OneFactor)
|
||||
assert.NoError(t, err)
|
||||
|
@ -173,10 +178,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
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.True(t, session.AuthenticationMethodRefs.MultiFactorAuthentication())
|
||||
|
@ -196,10 +201,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
||||
|
@ -208,10 +213,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
||||
|
@ -220,10 +225,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
||||
|
@ -232,10 +237,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
||||
|
@ -244,10 +249,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
||||
|
@ -256,10 +261,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
||||
|
@ -268,10 +273,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
session.SetTwoFactorTOTP(timeTwoFactor)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
||||
|
@ -280,10 +285,10 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
session.SetTwoFactorTOTP(timeTwoFactor)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
||||
|
@ -292,31 +297,28 @@ func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
|||
|
||||
func TestShouldDestroySessionAndWipeSessionData(t *testing.T) {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
configuration := schema.SessionConfiguration{}
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
domainSession, err := newTestSession()
|
||||
assert.NoError(t, err)
|
||||
|
||||
provider := NewProvider(configuration, nil)
|
||||
session, err := provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
session, err := domainSession.GetSession(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session.Username = testUsername
|
||||
session.AuthenticationLevel = authentication.TwoFactor
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
err = domainSession.SaveSession(ctx, session)
|
||||
assert.NoError(t, err)
|
||||
|
||||
newUserSession, err := provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
newUserSession, err := domainSession.GetSession(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testUsername, newUserSession.Username)
|
||||
assert.Equal(t, authentication.TwoFactor, newUserSession.AuthenticationLevel)
|
||||
|
||||
err = provider.DestroySession(ctx)
|
||||
require.NoError(t, err)
|
||||
err = domainSession.DestroySession(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
newUserSession, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
newUserSession, err = domainSession.GetSession(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", newUserSession.Username)
|
||||
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 (
|
||||
"time"
|
||||
|
||||
session "github.com/fasthttp/session/v2"
|
||||
"github.com/fasthttp/session/v2/providers/redis"
|
||||
"github.com/fasthttp/session/v2"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
|
@ -14,8 +13,6 @@ import (
|
|||
// ProviderConfig is the configuration used to create the session provider.
|
||||
type ProviderConfig struct {
|
||||
config session.Config
|
||||
redisConfig *redis.Config
|
||||
redisSentinelConfig *redis.FailoverConfig
|
||||
providerName string
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
|
||||
storage:
|
||||
encryption_key: a_not_so_secure_encryption_key
|
||||
|
|
|
@ -23,7 +23,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
|
||||
storage:
|
||||
encryption_key: a_not_so_secure_encryption_key
|
||||
|
|
|
@ -20,10 +20,13 @@ authentication_backend:
|
|||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
domain: example.com
|
||||
cookies:
|
||||
- name: 'authelia_session'
|
||||
domain: 'example.com'
|
||||
authelia_url: 'https://login.example.com'
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
|
||||
storage:
|
||||
encryption_key: a_not_so_secure_encryption_key
|
||||
|
|
|
@ -24,7 +24,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
|
||||
storage:
|
||||
encryption_key: a_not_so_secure_encryption_key
|
||||
|
|
|
@ -24,7 +24,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
|
||||
storage:
|
||||
encryption_key: a_not_so_secure_encryption_key
|
||||
|
|
|
@ -24,7 +24,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
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
|
||||
storage:
|
||||
|
|
|
@ -21,10 +21,12 @@ authentication_backend:
|
|||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
cookies:
|
||||
- name: 'authelia_session'
|
||||
domain: 'example.com'
|
||||
|
||||
storage:
|
||||
encryption_key: a_not_so_secure_encryption_key
|
||||
|
|
|
@ -23,7 +23,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
|
||||
storage:
|
||||
encryption_key: a_not_so_secure_encryption_key
|
||||
|
|
|
@ -102,7 +102,7 @@ session:
|
|||
- host: redis-sentinel-2
|
||||
port: 26379
|
||||
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
|
||||
regulation:
|
||||
max_retries: 3
|
||||
|
|
|
@ -38,7 +38,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
|
||||
storage:
|
||||
encryption_key: a_not_so_secure_encryption_key
|
||||
|
|
|
@ -24,7 +24,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
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
|
||||
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
|
||||
expiration: 3600 # 1 hour
|
||||
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
|
||||
storage:
|
||||
|
|
|
@ -23,7 +23,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
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
|
||||
storage:
|
||||
|
|
|
@ -16,10 +16,13 @@ authentication_backend:
|
|||
|
||||
session:
|
||||
secret: unsecure_session_secret
|
||||
domain: example.com
|
||||
|
||||
cookies:
|
||||
- domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
|
||||
# We use redis here to keep the users authenticated when Authelia restarts
|
||||
# It eases development.
|
||||
redis:
|
||||
|
|
|
@ -19,7 +19,7 @@ session:
|
|||
domain: example.com
|
||||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
remember_me: 1y
|
||||
# We use redis here to keep the users authenticated when Authelia restarts
|
||||
# It eases development.
|
||||
redis:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue