diff --git a/authentik/sources/oauth/apps.py b/authentik/sources/oauth/apps.py index b4812f85d..b3d6fd67e 100644 --- a/authentik/sources/oauth/apps.py +++ b/authentik/sources/oauth/apps.py @@ -17,6 +17,7 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [ "authentik.sources.oauth.types.reddit", "authentik.sources.oauth.types.twitter", "authentik.sources.oauth.types.mailcow", + "authentik.sources.oauth.types.twitch", ] diff --git a/authentik/sources/oauth/models.py b/authentik/sources/oauth/models.py index c4a84a8c6..8b2bf90c1 100644 --- a/authentik/sources/oauth/models.py +++ b/authentik/sources/oauth/models.py @@ -115,6 +115,16 @@ class GitHubOAuthSource(OAuthSource): verbose_name_plural = _("GitHub OAuth Sources") +class TwitchOAuthSource(OAuthSource): + """Social Login using Twitch.""" + + class Meta: + + abstract = True + verbose_name = _("Twitch OAuth Source") + verbose_name_plural = _("Twitch OAuth Sources") + + class MailcowOAuthSource(OAuthSource): """Social Login using Mailcow.""" diff --git a/authentik/sources/oauth/tests/test_type_twitch.py b/authentik/sources/oauth/tests/test_type_twitch.py new file mode 100644 index 000000000..f18e4eaf2 --- /dev/null +++ b/authentik/sources/oauth/tests/test_type_twitch.py @@ -0,0 +1,37 @@ +"""Twitch Type tests""" +from django.test import TestCase + +from authentik.sources.oauth.models import OAuthSource +from authentik.sources.oauth.types.twitch import TwitchOAuth2Callback + +# https://dev.twitch.tv/docs/authentication/getting-tokens-oidc/#getting-claims-information-from-an-access-token +TWITCH_USER = { + "aud": "ym2tq9o71tikh2zyebksiture1hzg5", + "exp": 1665261184, + "iat": 1665260184, + "iss": "https://id.twitch.tv/oauth2", + "sub": "603916897", + "email": "foo@bar.baz", + "preferred_username": "FooBar", +} + + +class TestTypeTwitch(TestCase): + """OAuth Source tests""" + + def setUp(self): + self.source = OAuthSource.objects.create( + name="test", + slug="test", + provider_type="twitch", + authorization_url="", + profile_url="", + consumer_key="", + ) + + def test_enroll_context(self): + """Test twitch Enrollment context""" + ak_context = TwitchOAuth2Callback().get_user_enroll_context(TWITCH_USER) + self.assertEqual(ak_context["username"], TWITCH_USER["preferred_username"]) + self.assertEqual(ak_context["email"], TWITCH_USER["email"]) + self.assertEqual(ak_context["name"], TWITCH_USER["preferred_username"]) diff --git a/authentik/sources/oauth/types/twitch.py b/authentik/sources/oauth/types/twitch.py new file mode 100644 index 000000000..5fa9fad74 --- /dev/null +++ b/authentik/sources/oauth/types/twitch.py @@ -0,0 +1,60 @@ +"""Twitch OAuth Views""" +from json import dumps +from typing import Any, Optional + +from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient +from authentik.sources.oauth.types.registry import SourceType, registry +from authentik.sources.oauth.views.callback import OAuthCallback +from authentik.sources.oauth.views.redirect import OAuthRedirect + + +class TwitchClient(UserprofileHeaderAuthClient): + """Twitch needs the token_type to be capitalized for the request header.""" + + def get_profile_info(self, token: dict[str, str]) -> Optional[dict[str, Any]]: + token["token_type"] = token["token_type"].capitalize() + return super().get_profile_info(token) + + +class TwitchOAuthRedirect(OAuthRedirect): + """Twitch OAuth2 Redirect""" + + def get_additional_parameters(self, source): # pragma: no cover + claims = {"userinfo": {"email": None, "preferred_username": None}} + return { + "scope": ["openid"], + "claims": dumps(claims), + } + + +class TwitchOAuth2Callback(OAuthCallback): + """Twitch OAuth2 Callback""" + + client_class = TwitchClient + + def get_user_id(self, info: dict[str, str]) -> str: + return info.get("sub", "") + + def get_user_enroll_context( + self, + info: dict[str, Any], + ) -> dict[str, Any]: + return { + "username": info.get("preferred_username"), + "email": info.get("email"), + "name": info.get("preferred_username"), + } + + +@registry.register() +class TwitchType(SourceType): + """Twitch Type definition""" + + callback_view = TwitchOAuth2Callback + redirect_view = TwitchOAuthRedirect + name = "Twitch" + slug = "twitch" + + authorization_url = "https://id.twitch.tv/oauth2/authorize" + access_token_url = "https://id.twitch.tv/oauth2/token" # nosec + profile_url = "https://id.twitch.tv/oauth2/userinfo" diff --git a/schema.yml b/schema.yml index 9e495ae95..47324b0fd 100644 --- a/schema.yml +++ b/schema.yml @@ -35104,6 +35104,7 @@ components: - reddit - twitter - mailcow + - twitch type: string ProxyMode: enum: diff --git a/web/authentik/sources/twitch.svg b/web/authentik/sources/twitch.svg new file mode 100644 index 000000000..bceb32678 --- /dev/null +++ b/web/authentik/sources/twitch.svg @@ -0,0 +1,19 @@ + + + + +Asset 2 + + + + + + + + + + + \ No newline at end of file diff --git a/website/integrations/sources/twitch/index.md b/website/integrations/sources/twitch/index.md new file mode 100644 index 000000000..1e49c7a8d --- /dev/null +++ b/website/integrations/sources/twitch/index.md @@ -0,0 +1,60 @@ +--- +title: Twitch +--- + +Support level: Community + +Allows users to authenticate using their Twitch credentials + +## Preparation + +The following placeholders will be used: + +- `authentik.company` is the FQDN of the authentik install. + +## Twitch + +1. Click **Register Your Application** in the Twitch Developers Console https://dev.twitch.tv/console + +![Register Your Application Button](twitch1.png) + +2. Name your Application + +3. Add https://authentik.company/source/oauth/callback/twitch in the **OAuth Redirect URLs** field + +4. Select a Category for your Application + +5. Click **Create** to finish the registration of your Application + +![Create Application](twitch2.png) + +6. Click **Manage** on your newly created Application + +![Manage Application](twitch3.png) + +7. Copy your Client ID and save it for later + +8. Click **New Secret** to create a new Secret + +9. Copy the above Secret and also save it for later + +![Copy Keys](twitch4.png) + +## authentik + +10. Under _Directory -> Federation & Social login_ Click **Create Twitch OAuth Source** + +11. **Name:** Choose a name (For the example I used Twitch) +12. **Slug:** twitch (You can choose a different slug, if you do you will need to update the Twitch redirect URL and point it to the correct slug.) +13. **Consumer Key:** Client ID from step 7 +14. **Consumer Secret:** Secret from step 9 + +Here is an example of a complete authentik Twitch OAuth Source + +![Authentik Source Example](twitch5.png) + +Save, and you now have Twitch as a source. + +:::note +For more details on how-to have the new source display on the Login Page see [here](../). +::: diff --git a/website/integrations/sources/twitch/twitch1.png b/website/integrations/sources/twitch/twitch1.png new file mode 100644 index 000000000..d6edb5f6a Binary files /dev/null and b/website/integrations/sources/twitch/twitch1.png differ diff --git a/website/integrations/sources/twitch/twitch2.png b/website/integrations/sources/twitch/twitch2.png new file mode 100644 index 000000000..281358d88 Binary files /dev/null and b/website/integrations/sources/twitch/twitch2.png differ diff --git a/website/integrations/sources/twitch/twitch3.png b/website/integrations/sources/twitch/twitch3.png new file mode 100644 index 000000000..bc51dd129 Binary files /dev/null and b/website/integrations/sources/twitch/twitch3.png differ diff --git a/website/integrations/sources/twitch/twitch4.png b/website/integrations/sources/twitch/twitch4.png new file mode 100644 index 000000000..a1a4fe238 Binary files /dev/null and b/website/integrations/sources/twitch/twitch4.png differ diff --git a/website/integrations/sources/twitch/twitch5.png b/website/integrations/sources/twitch/twitch5.png new file mode 100644 index 000000000..f6cbb4ed9 Binary files /dev/null and b/website/integrations/sources/twitch/twitch5.png differ diff --git a/website/sidebarsIntegrations.js b/website/sidebarsIntegrations.js index 772c24d23..6d71cec6c 100644 --- a/website/sidebarsIntegrations.js +++ b/website/sidebarsIntegrations.js @@ -137,6 +137,7 @@ module.exports = { "sources/github/index", "sources/google/index", "sources/mailcow/index", + "sources/twitch/index", "sources/plex/index", "sources/twitter/index", ],