Browse Source

Merge pull request #2079 from botisan-ai/okta-login

Added Okta Login for newer versions of Doccano
pull/2094/head
Hiroki Nakayama 1 year ago
committed by GitHub
parent
commit
a2918f792e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 146 additions and 8 deletions
  1. 10
      backend/config/settings/base.py
  2. 2
      backend/config/urls.py
  3. 6
      backend/pyproject.toml
  4. 0
      backend/social/__init__.py
  5. 0
      backend/social/admin.py
  6. 6
      backend/social/apps.py
  7. 0
      backend/social/models.py
  8. 9
      backend/social/okta.py
  9. 7
      backend/social/urls.py
  10. 7
      backend/social/v1_urls.py
  11. 26
      backend/social/views.py
  12. 46
      frontend/components/auth/SocialLogin.vue
  13. 2
      frontend/domain/models/auth/authRepository.ts
  14. 3
      frontend/i18n/de/user.js
  15. 3
      frontend/i18n/en/user.js
  16. 3
      frontend/i18n/fr/user.js
  17. 3
      frontend/i18n/zh/user.js
  18. 8
      frontend/pages/auth.vue
  19. 6
      frontend/repositories/auth/apiAuthRepository.ts
  20. 4
      frontend/services/application/auth/authApplicationService.ts
  21. 3
      frontend/store/auth.js

10
backend/config/settings/base.py

@ -59,8 +59,10 @@ INSTALLED_APPS = [
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.okta",
"dj_rest_auth",
"dj_rest_auth.registration",
"django.contrib.sites",
"django_celery_results",
"django_drf_filepond",
"health_check",
@ -280,3 +282,11 @@ CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
SOCIALACCOUNT_PROVIDERS = {
"okta": {
"OKTA_BASE_URL": env("OAUTH_OKTA_OAUTH2_API_URL", ""),
"OAUTH_PKCE_ENABLED": True,
"APP": {"client_id": env("OAUTH_OKTA_OAUTH2_KEY", ""), "secret": env("OAUTH_OKTA_OAUTH2_SECRET", "")},
}
}

2
backend/config/urls.py

@ -52,6 +52,8 @@ if settings.DEBUG or os.environ.get("STANDALONE", False):
urlpatterns += [
path("admin/", admin.site.urls),
path("api-auth/", include("rest_framework.urls")),
path("social/", include("social.urls")),
path("v1/social/", include("social.v1_urls")),
path("v1/health/", include("health_check.urls")),
path("v1/", include("api.urls")),
path("v1/", include("roles.urls")),

6
backend/pyproject.toml

@ -126,7 +126,8 @@ known_first_party = [
"metrics",
"projects",
"roles",
"users"
"users",
"social"
]
known_local_folder = [
"api",
@ -140,7 +141,8 @@ known_local_folder = [
"metrics",
"projects",
"roles",
"users"
"users",
"social"
]
[tool.taskipy.tasks]

0
backend/social/__init__.py

0
backend/social/admin.py

6
backend/social/apps.py

@ -0,0 +1,6 @@
from django.apps import AppConfig
class SocialConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "social"

0
backend/social/models.py

9
backend/social/okta.py

@ -0,0 +1,9 @@
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from allauth.socialaccount.providers.okta.views import OktaOAuth2Adapter
from dj_rest_auth.registration.views import SocialLoginView
class OktaLogin(SocialLoginView):
adapter_class = OktaOAuth2Adapter
callback_url = "/projects"
client_class = OAuth2Client

7
backend/social/urls.py

@ -0,0 +1,7 @@
from django.urls import path
from .okta import OktaLogin
urlpatterns = [
path("complete/okta-oauth2/", OktaLogin.as_view(), name="okta_login"),
]

7
backend/social/v1_urls.py

@ -0,0 +1,7 @@
from django.urls import path
from .views import Social
urlpatterns = [
path("links/", Social.as_view()),
]

26
backend/social/views.py

@ -0,0 +1,26 @@
from django.conf import settings
from rest_framework.response import Response
from rest_framework.views import APIView
class Social(APIView):
permission_classes = ()
def get(self, request, *args, **kwargs):
return Response(
{
"okta": {
"type": "oauth2",
"base_url": settings.SOCIALACCOUNT_PROVIDERS.get("okta").get("OKTA_BASE_URL"),
"client_id": settings.SOCIALACCOUNT_PROVIDERS.get("okta").get("APP").get("client_id"),
"redirect_path": "/social/complete/okta-oauth2",
"authorize_url": "https://"
+ settings.SOCIALACCOUNT_PROVIDERS.get("okta").get("OKTA_BASE_URL")
+ "/oauth2/v1/authorize?response_type=code&client_id="
+ settings.SOCIALACCOUNT_PROVIDERS.get("okta").get("APP").get("client_id")
+ "&scope=openid&state=unknown&response_mode=form_post",
}
if settings.SOCIALACCOUNT_PROVIDERS.get("okta").get("OKTA_BASE_URL")
else {},
}
)

46
frontend/components/auth/SocialLogin.vue

@ -0,0 +1,46 @@
<template>
<div>
<v-btn
v-for="item in social"
:key="item.provider"
block
elevation="2"
color="secondary"
:href="item.href"
class="mt-5"
>
{{ $t('user.socialLogin', { provider: item.provider }) }}
</v-btn>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
props: {
fetchSocialLink: {
type: Function,
default: () => Promise
}
},
data() {
return {
social: {}
}
},
async mounted() {
const response = await this.fetchSocialLink()
this.social = Object.entries(response)
.map(([key, value]: any) => ({
provider: key,
value
}))
.filter((item) => !!item.value?.authorize_url)
.map((item: any) => ({
...item,
href: `${item.value.authorize_url}&redirect_uri=${location.origin}${item.value.redirect_path}`
}))
}
})
</script>

2
frontend/domain/models/auth/authRepository.ts

@ -2,4 +2,6 @@ export interface AuthRepository {
login(username: string, password: string): Promise<void>
logout(): Promise<void>
socialLink(): Promise<any[]>
}

3
frontend/i18n/de/user.js

@ -2,5 +2,6 @@ export default {
login: 'Einloggen',
signOut: 'Ausloggen',
username: 'Benutzername',
password: 'Passwort'
password: 'Passwort',
socialLogin: 'Anmeldung über {provider}'
}

3
frontend/i18n/en/user.js

@ -2,5 +2,6 @@ export default {
login: 'Login',
signOut: 'Sign Out',
username: 'Username',
password: 'Password'
password: 'Password',
socialLogin: 'Login With {provider}'
}

3
frontend/i18n/fr/user.js

@ -2,5 +2,6 @@ export default {
login: 'Connexion',
signOut: 'Déconnexion',
username: "Nom d'utilisateur",
password: 'Mot de passe'
password: 'Mot de passe',
socialLogin: 'Connexion via {provider}'
}

3
frontend/i18n/zh/user.js

@ -2,5 +2,6 @@ export default {
login: '登录',
signOut: '注销',
username: '用户名',
password: '密码'
password: '密码',
socialLogin: '通过 {provider} 登录'
}

8
frontend/pages/auth.vue

@ -5,6 +5,8 @@
<v-row align="center" justify="center">
<v-col cols="12" sm="8" md="4">
<form-login :login="authenticateUser" />
<social-login :fetch-social-link="fetchSocialLink" />
</v-col>
</v-row>
</v-container>
@ -16,14 +18,16 @@
import Vue from 'vue'
import { mapActions } from 'vuex'
import FormLogin from '@/components/auth/FormLogin.vue'
import SocialLogin from '@/components/auth/SocialLogin.vue'
export default Vue.extend({
components: {
FormLogin
FormLogin,
SocialLogin
},
methods: {
...mapActions('auth', ['authenticateUser'])
...mapActions('auth', ['authenticateUser', 'fetchSocialLink'])
}
})
</script>

6
frontend/repositories/auth/apiAuthRepository.ts

@ -13,4 +13,10 @@ export class APIAuthRepository implements AuthRepository {
const url = '/auth/logout/'
await this.request.post(url)
}
async socialLink(): Promise<any[]> {
const url = '/social/links/'
const response = await this.request.get(url)
return response.data
}
}

4
frontend/services/application/auth/authApplicationService.ts

@ -10,4 +10,8 @@ export class AuthApplicationService {
public async logout() {
await this.repository.logout()
}
public async socialLink() {
return await this.repository.socialLink()
}
}

3
frontend/store/auth.js

@ -47,6 +47,9 @@ export const actions = {
throw new Error('The credential is invalid')
}
},
async fetchSocialLink() {
return await this.$services.auth.socialLink()
},
async initAuth({ commit }) {
try {
const user = await this.$services.user.getMyProfile()

Loading…
Cancel
Save