diff --git a/src/components/login.component.ts b/src/components/login.component.ts index b4a1e59..dfec865 100644 --- a/src/components/login.component.ts +++ b/src/components/login.component.ts @@ -21,9 +21,7 @@ export class LoginComponent { constructor ( private loginService: LoginService, - ) { - this.providers = [this.providers[0]] // only keep GH for now - } + ) { } async ngOnInit () { await this.loginService.ready$.toPromise() diff --git a/src/components/settingsModal.component.pug b/src/components/settingsModal.component.pug index 24f1ebe..3bfa6e8 100644 --- a/src/components/settingsModal.component.pug +++ b/src/components/settingsModal.component.pug @@ -1,8 +1,15 @@ .modal-header - h5.modal-title Settings + h3.modal-title Settings .modal-body .mb-3 + h5 GitHub account + a.btn.btn-info(href='/api/1/auth/social/login/github') + fa-icon([icon]='_githubIcon', [fixedWidth]='true') + span Connect a GitHub account + + .mb-3 + h5 Connection gateway .form-check.form-switch input.form-check-input( type='checkbox', diff --git a/src/components/settingsModal.component.ts b/src/components/settingsModal.component.ts index 318a573..68db3f4 100644 --- a/src/components/settingsModal.component.ts +++ b/src/components/settingsModal.component.ts @@ -4,6 +4,7 @@ import { LoginService } from '../services/login.service' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { User } from '../api' import { AppConnectorService } from '../services/appConnector.service' +import { faGithub } from '@fortawesome/free-brands-svg-icons' @Component({ selector: 'settings-modal', @@ -13,6 +14,7 @@ import { AppConnectorService } from '../services/appConnector.service' export class SettingsModalComponent { user: User customGatewayEnabled = false + _githubIcon = faGithub constructor ( public appConnector: AppConnectorService, diff --git a/tabby/app/api.py b/tabby/app/api.py index dc5e192..d42c030 100644 --- a/tabby/app/api.py +++ b/tabby/app/api.py @@ -1,6 +1,5 @@ import asyncio import random -from tabby.app.consumers import GatewayAdminConnection from django.conf import settings from django.contrib.auth import logout from dataclasses import dataclass @@ -14,8 +13,11 @@ from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.serializers import ModelSerializer, Serializer from rest_framework_dataclasses.serializers import DataclassSerializer +from social_django.models import UserSocialAuth from typing import List +from .consumers import GatewayAdminConnection +from .sponsors import get_sponsor_usernames from .models import Config, Gateway, User @@ -98,6 +100,7 @@ class AppVersionViewSet(ListModelMixin, GenericViewSet): class UserSerializer(ModelSerializer): id = fields.IntegerField() is_pro = fields.SerializerMethodField() + github_username = fields.SerializerMethodField() class Meta: model = User @@ -108,11 +111,22 @@ class UserSerializer(ModelSerializer): 'custom_connection_gateway', 'custom_connection_gateway_token', 'is_pro', + 'github_username', ) read_only_fields = ('id', 'username') def get_is_pro(self, obj): - return False + username = self.get_github_username(obj) + if not username: + return False + return username in get_sponsor_usernames() + + def get_github_username(self, obj): + social_auth = UserSocialAuth.objects.filter(user=obj, provider='github').first() + if not social_auth: + return None + + return social_auth.extra_data.get('login') class UserViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet): diff --git a/tabby/app/models.py b/tabby/app/models.py index 01f877f..bc177b7 100644 --- a/tabby/app/models.py +++ b/tabby/app/models.py @@ -16,8 +16,8 @@ class Config(models.Model): class User(AbstractUser): active_config = models.ForeignKey(Config, null=True, on_delete=models.SET_NULL, related_name='+') active_version = models.CharField(max_length=32, null=True) - custom_connection_gateway = models.CharField(max_length=255, null=True) - custom_connection_gateway_token = models.CharField(max_length=255, null=True) + custom_connection_gateway = models.CharField(max_length=255, null=True, blank=True) + custom_connection_gateway_token = models.CharField(max_length=255, null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) modified_at = models.DateTimeField(auto_now=True) diff --git a/tabby/app/sponsors.py b/tabby/app/sponsors.py index ebc5bdb..3330bcd 100644 --- a/tabby/app/sponsors.py +++ b/tabby/app/sponsors.py @@ -1,12 +1,14 @@ from django.conf import settings +from django.core.cache import cache from gql import Client, gql from gql.transport.requests import RequestsHTTPTransport GQL_ENDPOINT = 'https://api.github.com/graphql' +CACHE_KEY = 'cached-sponsors' -def get_sponsor_usernames(): +def fetch_sponsor_usernames(): client = Client( transport=RequestsHTTPTransport( url=GQL_ENDPOINT, @@ -61,3 +63,9 @@ def get_sponsor_usernames(): result.append(node['sponsor']['login']) return result + + +def get_sponsor_usernames(): + if not cache.get(CACHE_KEY): + cache.set(CACHE_KEY, fetch_sponsor_usernames(), timeout=30) + return cache.get(CACHE_KEY) diff --git a/tabby/settings.py b/tabby/settings.py index 9ab1545..c76a9af 100644 --- a/tabby/settings.py +++ b/tabby/settings.py @@ -70,17 +70,15 @@ TEMPLATES = [ ASGI_APPLICATION = 'tabby.asgi.application' WSGI_APPLICATION = 'tabby.wsgi.application' - -# Database -# https://docs.djangoproject.com/en/3.2/ref/settings/#databases - DATABASES = { 'default': dj_database_url.config(conn_max_age=600) } - -# Password validation -# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + } +} AUTH_PASSWORD_VALIDATORS = [ { @@ -163,6 +161,18 @@ AUTHENTICATION_BACKENDS = ( ) SOCIAL_AUTH_GITHUB_SCOPE = ['read:user', 'user:email'] +SOCIAL_AUTH_PIPELINE = ( + 'social_core.pipeline.social_auth.social_details', + 'social_core.pipeline.social_auth.social_uid', + 'social_core.pipeline.social_auth.auth_allowed', + 'social_core.pipeline.social_auth.social_user', + 'social_core.pipeline.user.get_username', + 'social_core.pipeline.social_auth.associate_by_email', + 'social_core.pipeline.user.create_user', + 'social_core.pipeline.social_auth.associate_user', + 'social_core.pipeline.social_auth.load_extra_data', + 'social_core.pipeline.user.user_details', +) LOGIN_REDIRECT_URL = '/app'