Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Login with OAuth via OpenID Connect (OIDC) #3280

Merged
merged 75 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
e4e8167
initial oidc implementation
cmintey Dec 16, 2023
41fcf4b
add dynamic scheme
cmintey Dec 17, 2023
48325f9
e2e test setup
cmintey Dec 17, 2023
6f163bc
add caching
cmintey Dec 17, 2023
9560fdc
fix
cmintey Dec 17, 2023
a5cf200
try this
cmintey Dec 17, 2023
3286e15
add libldap-2.5 to runtime dependencies (#2849)
cmintey Dec 15, 2023
082e725
New translations en-us.json (Norwegian) (#2851)
hay-kot Dec 15, 2023
8a3a780
New Crowdin updates (#2855)
hay-kot Dec 16, 2023
1dd79d7
fix
cmintey Dec 17, 2023
854910d
remove cache
cmintey Dec 17, 2023
6c2b2e0
cache yarn deps
cmintey Dec 17, 2023
b14e755
cache docker image
cmintey Dec 17, 2023
de77448
cleanup action
cmintey Dec 17, 2023
1971a11
lint
cmintey Dec 17, 2023
e13b721
fix tests
cmintey Dec 17, 2023
bb1a7b2
remove not needed variables
cmintey Dec 17, 2023
9c74bbf
run code gen
cmintey Dec 17, 2023
89805e7
fix tests
cmintey Dec 17, 2023
c398183
add docs
cmintey Dec 18, 2023
66ce003
move code into custom scheme
cmintey Dec 18, 2023
64c0ce8
Merge remote-tracking branch 'origin/mealie-next' into oidc
cmintey Dec 18, 2023
f00be6b
remove unneeded type
cmintey Dec 18, 2023
79c6d3c
fix oidc admin
cmintey Dec 18, 2023
1f828ac
add more tests
cmintey Dec 18, 2023
63bdccb
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Dec 21, 2023
db40857
add better spacing on login page
cmintey Dec 21, 2023
dfaa93f
create auth providers
cmintey Dec 28, 2023
1a847cd
clean up testing stuff
cmintey Dec 28, 2023
62dbe77
type fixes
cmintey Dec 28, 2023
5132e21
add OIDC auth method to postgres enum
cmintey Dec 28, 2023
df3fd80
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Jan 1, 2024
131fb29
add option to bypass login screen and go directly to iDP
cmintey Jan 1, 2024
b06598a
remove check so we can fallback to another auth method oauth fails
cmintey Jan 4, 2024
2ff8ef9
Add provider name to be shown at the login screen
cmintey Jan 4, 2024
f174e2b
add new properties to admin about api
cmintey Jan 4, 2024
763b676
fix spec
cmintey Jan 4, 2024
473b889
add a prompt to change auth method when changing password
cmintey Jan 10, 2024
4eb0e0b
Create new auth section. Add more info on auth methods
cmintey Jan 10, 2024
6ef3f2f
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Jan 20, 2024
df28c61
update docs
cmintey Jan 20, 2024
4c1502b
run ruff
cmintey Jan 20, 2024
dc95517
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Jan 20, 2024
ee2dbd6
update docs
cmintey Jan 20, 2024
bcfa861
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Jan 26, 2024
cd9410c
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Feb 11, 2024
3df1987
format
cmintey Feb 11, 2024
fcdc6d4
docs gen
cmintey Feb 11, 2024
b1afb94
formatting
cmintey Feb 11, 2024
fc68768
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Feb 13, 2024
a229082
initialize logger in class
cmintey Feb 13, 2024
c66a430
mypy type fixes
cmintey Feb 13, 2024
5456475
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Feb 23, 2024
4513b92
docs gen
cmintey Feb 23, 2024
68ef897
add models to get proper fields in docs and fix serialization
cmintey Feb 23, 2024
122fbf7
validate id token before using it
cmintey Feb 23, 2024
0331515
only request a mealie token on initial callback
cmintey Feb 23, 2024
ae5bbff
remove unused method
cmintey Feb 23, 2024
7fd0cee
fix unit tests
cmintey Feb 23, 2024
43b0e68
docs gen
cmintey Feb 23, 2024
5f33229
check for valid idToken before getting token
cmintey Feb 23, 2024
fbd73df
add iss to mealie token
cmintey Feb 24, 2024
d0f0318
check to see if we already have a mealie token before getting one
cmintey Feb 24, 2024
95a5a03
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Feb 24, 2024
189392c
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Feb 26, 2024
b5d77b5
fix lock file
cmintey Feb 26, 2024
0b0d2cb
update authlib
cmintey Feb 26, 2024
4aa5750
Merge remote-tracking branch 'upstream/mealie-next' into oidc
cmintey Mar 1, 2024
f3c5f28
update lock file
cmintey Mar 1, 2024
501aa27
add remember me environment variable
cmintey Mar 1, 2024
fdfbb06
add user group setting to allow only certain groups to log in
cmintey Mar 3, 2024
b23bec8
Merge branch 'mealie-next' into oidc
hay-kot Mar 10, 2024
fe10a5c
Merge branch 'mealie-next' into prs/pr-2860-fix-conflicts
hay-kot Mar 10, 2024
96e5a0c
speficy ignore rules for ruff error
hay-kot Mar 10, 2024
e93fcd4
re-order migrations
hay-kot Mar 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: E2E Tests
on:
pull_request:
branches:
- mealie-next
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./tests/e2e
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'yarn'
cache-dependency-path: ./tests/e2e/yarn.lock
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Image
uses: docker/build-push-action@v5
with:
file: ./docker/Dockerfile
context: .
push: false
load: true
tags: mealie:e2e
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy E2E Test Environment
run: docker compose up -d
working-directory: ./tests/e2e/docker
- name: Install dependencies
run: npm install -g yarn && yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Check test environment
run: docker ps
- name: Run Playwright tests
run: yarn playwright test
- name: Destroy Test Environment
if: always()
run: docker compose down --volumes
working-directory: ./tests/e2e/docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""add OIDC auth method

Revision ID: 09aba125b57a
Revises: 2298bb460ffd
Create Date: 2024-03-10 05:08:32.397027

"""

import sqlalchemy as sa

import mealie.db.migration_types
from alembic import op

# revision identifiers, used by Alembic.
revision = "09aba125b57a"
down_revision = "2298bb460ffd"
branch_labels = None
depends_on = None


def is_postgres():
return op.get_context().dialect.name == "postgresql"


def upgrade():
if is_postgres():
op.execute("ALTER TYPE authmethod ADD VALUE 'OIDC'")


def downgrade():
pass
88 changes: 88 additions & 0 deletions docs/docs/documentation/getting-started/authentication/oidc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# OpenID Connect (OIDC) Authentication

Mealie supports 3rd party authentication via [OpenID Connect (OIDC)](https://openid.net/connect/), an identity layer built on top of OAuth2. OIDC is supported by many identity providers, including:

- [Authentik](https://goauthentik.io/integrations/sources/oauth/#openid-connect)
- [Authelia](https://www.authelia.com/configuration/identity-providers/open-id-connect/)
- [Keycloak](https://www.keycloak.org/docs/latest/securing_apps/#_oidc)
- [Okta](https://www.okta.com/openid-connect/)

## Account Linking

Signing in with OAuth will automatically find your account in Mealie and link to it. If a user does not exist in Mealie, then one will be created (if enabled), but will be unable to log in with any other authentication method. An admin can configure another authentication method for such a user.

## Provider Setup

Before you can start using OIDC Authentication, you must first configure a new client application in your identity provider. Your identity provider must support the OAuth **Authorization Code** flow (with PKCE). The steps will vary by provider, but generally, the steps are as follows.

1. Create a new client application
- The Provider type should be OIDC or OAuth2
- The Grant type should be `Authorization Code`
- The Application type should be `Web`
- The Client type should be `public`

2. Configure redirect URI

The only redirect URI that is needed is `http(s)://DOMAIN:PORT/login`

The redirect URI should include any URL that Mealie is accessible from. Some examples include

http://localhost:9091/login
https://mealie.example.com/login

3. Configure origins

If your identity provider enforces CORS on any endpoints, you will need to specify your Mealie URL as an Allowed Origin.

4. Configure allowed scopes

The scopes required are `openid profile email groups`

## Mealie Setup

Take the client id and your discovery URL and update your environment variables to include the required OIDC variables described in [Installation - Backend Configuration](../installation/backend-config.md#openid-connect-oidc)

## Examples

### Authelia

Follow the instructions in [Authelia's documentation](https://www.authelia.com/configuration/identity-providers/open-id-connect/). Below is an example config

!!! warning

This is only an example and not meant to be an exhaustive configuration. You should read read through the documentation and adjust your configuration as needed.

```yaml
identity_providers:
oidc:
access_token_lifespan: 1h
authorize_code_lifespan: 1m
id_token_lifespan: 1h
refresh_token_lifespan: 90m
enable_client_debug_messages: false
enforce_pkce: public_clients_only
cors:
endpoints:
- authorization
- token
- revocation
- introspection
allowed_origins:
- https://mealie.example.com
allowed_origins_from_client_redirect_uris: false
clients:
- id: mealie
description: Mealie
authorization_policy: one_factor
redirect_uris:
- https://mealie.example.com/login
public: true
grant_types:
- authorization_code
scopes:
- openid
- profile
- groups
- email
- offline_access
```
4 changes: 4 additions & 0 deletions docs/docs/documentation/getting-started/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ docker exec -it mealie-next bash
python /app/mealie/scripts/change_password.py
```

## I can't log in with external auth. How can I change my authentication method?

Follow the [steps above](#how-can-i-change-my-password) for changing your password. You will be prompted if you would like to switch your authentication method back to local auth so you can log in again.

## How do private groups and recipes work?

Managing private groups and recipes can be confusing. The following diagram and notes should help explain how they work to determine if a recipe can be shared publicly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ Changing the webworker settings may cause unforeseen memory leak issues with Mea
| LDAP_NAME_ATTRIBUTE | name | The LDAP attribute that maps to the user's name |
| LDAP_MAIL_ATTRIBUTE | mail | The LDAP attribute that maps to the user's email |

### OpenID Connect (OIDC)

For usage, see [Usage - OpenID Connect](../authentication/oidc.md)

| Variables | Default | Description |
| --- | :--: | --- |
| OIDC_AUTH_ENABLED | False | Enables authentication via OpenID Connect |
| OIDC_SIGNUP_ENABLED | True | Enables new users to be created when signing in for the first time with OIDC |
| OIDC_CONFIGURATION_URL | None | The URL to the OIDC configuration of your provider. This is usually something like https://auth.example.com/.well-known/openid-configuration |
| OIDC_CLIENT_ID | None | The client id of your configured client in your provider |
| OIDC_USER_GROUP| None | If specified, this group must be present in the user's group claim in order to authenticate |
| OIDC_ADMIN_GROUP | None | If this group is present in the group claims, the user will be set as an admin |
| OIDC_AUTO_REDIRECT | False | If `True`, then the login page will be bypassed an you will be sent directly to your Identity Provider. You can still get to the login page by adding `?direct=1` to the login URL |
| OIDC_PROVIDER_NAME | OAuth | The provider name is shown in SSO login button. "Login with <OIDC_PROVIDER_NAME\>" |
| OIDC_REMEMBER_ME | False | Because redirects bypass the login screen, you cant extend your session by clicking the "Remember Me" checkbox. By setting this value to true, a session will be extended as if "Remember Me" was checked |

### Themeing

Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Permissions and Public Access

Mealie provides various levels of user access and permissions. This includes:
- Authentication and registration ([check out the LDAP guide](./ldap.md) for how to configure access using LDAP)
- Authentication and registration ([LDAP](../authentication/ldap.md) and [OpenID Connect](../authentication/oidc.md) are both supported)
- Customizable user permissions
- Fine-tuned public access for non-users

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/overrides/api.html

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ nav:
- Backend Configuration: "documentation/getting-started/installation/backend-config.md"
- Usage:
- Backup and Restoring: "documentation/getting-started/usage/backups-and-restoring.md"
- LDAP Authentication: "documentation/getting-started/usage/ldap.md"
- Permissions and Public Access: "documentation/getting-started/usage/permissions-and-public-access.md"

- Authentication:
- LDAP: "documentation/getting-started/authentication/ldap.md"
- OpenID Connect: "documentation/getting-started/authentication/oidc.md"

- Community Guides:
- iOS Shortcuts: "documentation/community-guide/ios.md"
Expand Down
2 changes: 1 addition & 1 deletion frontend/composables/use-users/user-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const useUserForm = () => {
type: fieldTypes.SELECT,
hint: i18n.tc("user.authentication-method-hint"),
disableCreate: true,
options: [{ text: "Mealie" }, { text: "LDAP" }],
options: [{ text: "Mealie" }, { text: "LDAP" }, { text: "OIDC" }],
},
{
section: i18n.tc("user.permissions"),
Expand Down
7 changes: 6 additions & 1 deletion frontend/lang/messages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,10 @@
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
"ldap-ready-success-text": "Required LDAP variables are all set.",
"build": "Build",
"recipe-scraper-version": "Recipe Scraper Version"
"recipe-scraper-version": "Recipe Scraper Version",
"oidc-ready": "OIDC Ready",
"oidc-ready-error-text": "Not all OIDC Values are configured. This can be ignored if you are not using OIDC Authentication.",
"oidc-ready-success-text": "Required OIDC variables are all set."
},
"shopping-list": {
"all-lists": "All Lists",
Expand Down Expand Up @@ -836,6 +839,8 @@
"link-id": "Link ID",
"link-name": "Link Name",
"login": "Login",
"login-oidc": "Login with",
"or": "or",
"logout": "Logout",
"manage-users": "Manage Users",
"new-password": "New Password",
Expand Down
11 changes: 11 additions & 0 deletions frontend/lib/api/types/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export interface AdminAboutInfo {
version: string;
demoStatus: boolean;
allowSignup: boolean;
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
versionLatest: string;
apiPort: number;
apiDocs: boolean;
Expand All @@ -34,6 +37,9 @@ export interface AppInfo {
demoStatus: boolean;
allowSignup: boolean;
defaultGroupSlug?: string;
enableOidc: boolean;
oidcRedirect: boolean;
oidcProviderName: string;
}
export interface AppStartupInfo {
isFirstLogin: boolean;
Expand Down Expand Up @@ -72,6 +78,7 @@ export interface BackupOptions {
export interface CheckAppConfig {
emailReady: boolean;
ldapReady: boolean;
oidcReady: boolean;
baseUrlSet: boolean;
isUpToDate: boolean;
}
Expand Down Expand Up @@ -218,6 +225,10 @@ export interface NotificationImport {
status: boolean;
exception?: string;
}
export interface OIDCInfo {
configurationUrl?: string;
clientId?: string;
}
export interface RecipeImport {
name: string;
status: boolean;
Expand Down
2 changes: 1 addition & 1 deletion frontend/lib/api/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/

export type AuthMethod = "Mealie" | "LDAP";
export type AuthMethod = "Mealie" | "LDAP" | "OIDC";

export interface ChangePassword {
currentPassword: string;
Expand Down
11 changes: 10 additions & 1 deletion frontend/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export default {
auth: {
redirect: {
login: "/login",
logout: "/login",
logout: "/login?direct=1",
callback: "/login",
home: "/",
},
Expand All @@ -134,6 +134,7 @@ export default {
path: "/",
},
},
rewriteRedirects: false,
// Options
strategies: {
local: {
Expand All @@ -158,6 +159,14 @@ export default {
user: { url: "api/users/self", method: "get" },
},
},
oidc: {
scheme: "~/schemes/DynamicOpenIDConnectScheme",
resetOnError: true,
clientId: "",
endpoints: {
configuration: "",
}
},
},
},

Expand Down
2 changes: 1 addition & 1 deletion frontend/pages/admin/manage/users/_id.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default defineComponent({

const user = ref<UserOut | null>(null);
const disabledFields = computed(() => {
return user.value?.authMethod === "LDAP" ? ["admin"] : [];
return user.value?.authMethod !== "Mealie" ? ["admin"] : [];
})

const userError = ref(false);
Expand Down
10 changes: 10 additions & 0 deletions frontend/pages/admin/site-settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export default defineComponent({
isSiteSecure: true,
isUpToDate: false,
ldapReady: false,
oidcReady: false,
});
function isLocalHostOrHttps() {
return window.location.hostname === "localhost" || window.location.protocol === "https:";
Expand Down Expand Up @@ -258,6 +259,15 @@ export default defineComponent({
color: appConfig.value.ldapReady ? goodColor : warningColor,
icon: appConfig.value.ldapReady ? goodIcon : warningIcon,
},
{
id: "oidc-ready",
text: i18n.t("settings.oidc-ready"),
status: appConfig.value.oidcReady,
errorText: i18n.t("settings.oidc-ready-error-text"),
successText: i18n.t("settings.oidc-ready-success-text"),
color: appConfig.value.oidcReady ? goodColor : warningColor,
icon: appConfig.value.oidcReady ? goodIcon : warningIcon,
},
];
return data;
});
Expand Down
Loading
Loading