This commit is contained in:
Eugene Pankov 2022-11-07 18:56:10 +01:00
parent 05b476aa23
commit 99264d2bfc
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
24 changed files with 476 additions and 313 deletions

View File

@ -6,7 +6,7 @@ import sys
def main(): def main():
"""Run administrative tasks.""" """Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tabby.settings') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tabby.settings")
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
except ImportError as exc: except ImportError as exc:
@ -18,5 +18,5 @@ def main():
execute_from_command_line(sys.argv) execute_from_command_line(sys.argv)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -5,7 +5,15 @@ from .models import Gateway, User, Config
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
fieldsets = UserAdmin.fieldsets + ( fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('custom_connection_gateway', 'custom_connection_gateway_token')}), (
None,
{
"fields": (
"custom_connection_gateway",
"custom_connection_gateway_token",
)
},
),
) )

View File

@ -4,14 +4,17 @@ from . import app_version, auth, config, gateway, user
router = routers.DefaultRouter(trailing_slash=False) router = routers.DefaultRouter(trailing_slash=False)
router.register('api/1/configs', config.ConfigViewSet) router.register("api/1/configs", config.ConfigViewSet)
router.register('api/1/versions', app_version.AppVersionViewSet, basename='app-versions') router.register(
"api/1/versions", app_version.AppVersionViewSet, basename="app-versions"
)
urlpatterns = [ urlpatterns = [
path('api/1/auth/logout', auth.LogoutView.as_view()), path("api/1/auth/logout", auth.LogoutView.as_view()),
path('api/1/user', user.UserViewSet.as_view({'get': 'retrieve', 'put': 'update'})), path("api/1/user", user.UserViewSet.as_view({"get": "retrieve", "put": "update"})),
path('api/1/gateways/choose', gateway.ChooseGatewayViewSet.as_view({'post': 'retrieve'})), path(
"api/1/gateways/choose",
gateway.ChooseGatewayViewSet.as_view({"post": "retrieve"}),
path('', include(router.urls)), ),
path("", include(router.urls)),
] ]

View File

@ -25,27 +25,28 @@ class AppVersionSerializer(DataclassSerializer):
class AppVersionViewSet(ListModelMixin, GenericViewSet): class AppVersionViewSet(ListModelMixin, GenericViewSet):
serializer_class = AppVersionSerializer serializer_class = AppVersionSerializer
lookup_field = 'id' lookup_field = "id"
lookup_value_regex = r'[\w\d.-]+' lookup_value_regex = r"[\w\d.-]+"
queryset = '' queryset = ""
def _get_versions(self): def _get_versions(self):
fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme) fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme)
return [ return [
self._get_version(x['name']) self._get_version(x["name"])
for x in fs.listdir(settings.APP_DIST_STORAGE) for x in fs.listdir(settings.APP_DIST_STORAGE)
if x['type'] == 'directory' if x["type"] == "directory"
] ]
def _get_version(self, dir): def _get_version(self, dir):
fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme) fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme)
plugins = [ plugins = [
os.path.basename(x['name']) os.path.basename(x["name"])
for x in fs.listdir(dir) for x in fs.listdir(dir)
if x['type'] == 'directory' and os.path.basename(x['name']) if x["type"] == "directory"
and os.path.basename(x["name"])
not in [ not in [
'tabby-web-container', "tabby-web-container",
'tabby-web-demo', "tabby-web-demo",
] ]
] ]

View File

@ -10,8 +10,8 @@ class ConfigSerializer(ModelSerializer):
class Meta: class Meta:
model = Config model = Config
read_only_fields = ('user', 'created_at', 'modified_at') read_only_fields = ("user", "created_at", "modified_at")
fields = '__all__' fields = "__all__"
class ConfigViewSet(ModelViewSet): class ConfigViewSet(ModelViewSet):

View File

@ -14,7 +14,7 @@ class GatewaySerializer(ModelSerializer):
auth_token = fields.CharField() auth_token = fields.CharField()
class Meta: class Meta:
fields = '__all__' fields = "__all__"
model = Gateway model = Gateway
def get_url(self, gw): def get_url(self, gw):
@ -23,8 +23,8 @@ class GatewaySerializer(ModelSerializer):
class NoGatewaysError(APIException): class NoGatewaysError(APIException):
status_code = status.HTTP_503_SERVICE_UNAVAILABLE status_code = status.HTTP_503_SERVICE_UNAVAILABLE
default_detail = 'No connection gateways available.' default_detail = "No connection gateways available."
default_code = 'no_gateways' default_code = "no_gateways"
class ChooseGatewayViewSet(RetrieveModelMixin, GenericViewSet): class ChooseGatewayViewSet(RetrieveModelMixin, GenericViewSet):

View File

@ -19,30 +19,34 @@ class UserSerializer(ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = ( fields = (
'id', "id",
'username', "username",
'active_config', "active_config",
'custom_connection_gateway', "custom_connection_gateway",
'custom_connection_gateway_token', "custom_connection_gateway_token",
'config_sync_token', "config_sync_token",
'is_pro', "is_pro",
'is_sponsor', "is_sponsor",
'github_username', "github_username",
) )
read_only_fields = ('id', 'username') read_only_fields = ("id", "username")
def get_is_pro(self, obj): def get_is_pro(self, obj):
return obj.force_pro or not settings.GITHUB_ELIGIBLE_SPONSORSHIPS or check_is_sponsor_cached(obj) return (
obj.force_pro
or not settings.GITHUB_ELIGIBLE_SPONSORSHIPS
or check_is_sponsor_cached(obj)
)
def get_is_sponsor(self, obj): def get_is_sponsor(self, obj):
return check_is_sponsor_cached(obj) return check_is_sponsor_cached(obj)
def get_github_username(self, obj): def get_github_username(self, obj):
social_auth = UserSocialAuth.objects.filter(user=obj, provider='github').first() social_auth = UserSocialAuth.objects.filter(user=obj, provider="github").first()
if not social_auth: if not social_auth:
return None return None
return social_auth.extra_data.get('login') return social_auth.extra_data.get("login")
class UserViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet): class UserViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):

View File

@ -2,5 +2,5 @@ from django.apps import AppConfig
class AppConfig(AppConfig): class AppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = "django.db.models.BigAutoField"
name = 'tabby.app' name = "tabby.app"

View File

@ -27,8 +27,8 @@ class GatewayConnection:
ctx.verify_mode = ssl.CERT_REQUIRED ctx.verify_mode = ssl.CERT_REQUIRED
GatewayConnection._ssl_context = ctx GatewayConnection._ssl_context = ctx
proto = 'wss' if GatewayConnection._ssl_context else 'ws' proto = "wss" if GatewayConnection._ssl_context else "ws"
self.url = f'{proto}://localhost:9000/connect/{quote(host)}:{quote(str(port))}' self.url = f"{proto}://localhost:9000/connect/{quote(host)}:{quote(str(port))}"
async def connect(self): async def connect(self):
self.context = websockets.connect(self.url, ssl=GatewayConnection._ssl_context) self.context = websockets.connect(self.url, ssl=GatewayConnection._ssl_context)
@ -53,7 +53,9 @@ class GatewayAdminConnection:
def __init__(self, gateway: Gateway): def __init__(self, gateway: Gateway):
if not settings.CONNECTION_GATEWAY_AUTH_KEY: if not settings.CONNECTION_GATEWAY_AUTH_KEY:
raise RuntimeError('CONNECTION_GATEWAY_AUTH_KEY is required to manage connection gateways') raise RuntimeError(
"CONNECTION_GATEWAY_AUTH_KEY is required to manage connection gateways"
)
if not GatewayAdminConnection._ssl_context: if not GatewayAdminConnection._ssl_context:
ctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) ctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
ctx.load_cert_chain( ctx.load_cert_chain(
@ -67,10 +69,12 @@ class GatewayAdminConnection:
ctx.verify_mode = ssl.CERT_REQUIRED ctx.verify_mode = ssl.CERT_REQUIRED
GatewayAdminConnection._ssl_context = ctx GatewayAdminConnection._ssl_context = ctx
self.url = f'wss://{gateway.host}:{gateway.admin_port}' self.url = f"wss://{gateway.host}:{gateway.admin_port}"
async def connect(self): async def connect(self):
self.context = websockets.connect(self.url, ssl=GatewayAdminConnection._ssl_context) self.context = websockets.connect(
self.url, ssl=GatewayAdminConnection._ssl_context
)
try: try:
self.socket = await self.context.__aenter__() self.socket = await self.context.__aenter__()
except OSError: except OSError:
@ -78,10 +82,14 @@ class GatewayAdminConnection:
async def authorize_client(self) -> str: async def authorize_client(self) -> str:
token = secrets.token_hex(32) token = secrets.token_hex(32)
await self.send(json.dumps({ await self.send(
'_': 'authorize-client', json.dumps(
'token': token, {
})) "_": "authorize-client",
"token": token,
}
)
)
return token return token
async def send(self, data): async def send(self, data):

View File

@ -11,52 +11,52 @@ from urllib.parse import urlparse
class Command(BaseCommand): class Command(BaseCommand):
help = 'Downloads a new app version' help = "Downloads a new app version"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('version', type=str) parser.add_argument("version", type=str)
def handle(self, *args, **options): def handle(self, *args, **options):
version = options['version'] version = options["version"]
target = f'{settings.APP_DIST_STORAGE}/{version}' target = f"{settings.APP_DIST_STORAGE}/{version}"
fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme) fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme)
plugin_list = [ plugin_list = [
'tabby-web-container', "tabby-web-container",
'tabby-core', "tabby-core",
'tabby-settings', "tabby-settings",
'tabby-terminal', "tabby-terminal",
'tabby-ssh', "tabby-ssh",
'tabby-community-color-schemes', "tabby-community-color-schemes",
'tabby-serial', "tabby-serial",
'tabby-telnet', "tabby-telnet",
'tabby-web', "tabby-web",
'tabby-web-demo', "tabby-web-demo",
] ]
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
tempdir = Path(tempdir) tempdir = Path(tempdir)
for plugin in plugin_list: for plugin in plugin_list:
logging.info(f'Resolving {plugin}@{version}') logging.info(f"Resolving {plugin}@{version}")
response = requests.get(f'{settings.NPM_REGISTRY}/{plugin}/{version}') response = requests.get(f"{settings.NPM_REGISTRY}/{plugin}/{version}")
response.raise_for_status() response.raise_for_status()
info = response.json() info = response.json()
url = info['dist']['tarball'] url = info["dist"]["tarball"]
logging.info(f'Downloading {plugin}@{version} from {url}') logging.info(f"Downloading {plugin}@{version} from {url}")
response = requests.get(url) response = requests.get(url)
with tempfile.NamedTemporaryFile('wb') as f: with tempfile.NamedTemporaryFile("wb") as f:
f.write(response.content) f.write(response.content)
plugin_final_target = Path(tempdir) / plugin plugin_final_target = Path(tempdir) / plugin
with tempfile.TemporaryDirectory() as extraction_tmp: with tempfile.TemporaryDirectory() as extraction_tmp:
subprocess.check_call( subprocess.check_call(
['tar', '-xzf', f.name, '-C', str(extraction_tmp)] ["tar", "-xzf", f.name, "-C", str(extraction_tmp)]
) )
shutil.move( shutil.move(
Path(extraction_tmp) / 'package', plugin_final_target Path(extraction_tmp) / "package", plugin_final_target
) )
if fs.exists(target): if fs.exists(target):

View File

@ -13,63 +13,171 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('auth', '0012_alter_user_first_name_max_length'), ("auth", "0012_alter_user_first_name_max_length"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='User', name="User",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('password', models.CharField(max_length=128, verbose_name='password')), "id",
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), models.BigAutoField(
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), auto_created=True,
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), primary_key=True,
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), serialize=False,
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), verbose_name="ID",
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), ),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ("password", models.CharField(max_length=128, verbose_name="password")),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), (
('active_version', models.CharField(max_length=32, null=True)), "last_login",
('custom_connection_gateway', models.CharField(max_length=255, null=True, blank=True)), models.DateTimeField(
('custom_connection_gateway_token', models.CharField(max_length=255, null=True, blank=True)), blank=True, null=True, verbose_name="last login"
('created_at', models.DateTimeField(auto_now_add=True)), ),
('modified_at', models.DateTimeField(auto_now=True)), ),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
verbose_name="username",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
("active_version", models.CharField(max_length=32, 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)),
], ],
options={ options={
'verbose_name': 'user', "verbose_name": "user",
'verbose_name_plural': 'users', "verbose_name_plural": "users",
'abstract': False, "abstract": False,
}, },
managers=[ managers=[
('objects', django.contrib.auth.models.UserManager()), ("objects", django.contrib.auth.models.UserManager()),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Config', name="Config",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('content', models.TextField(default='{}')), "id",
('last_used_with_version', models.CharField(max_length=32, null=True)), models.BigAutoField(
('created_at', models.DateTimeField(auto_now_add=True)), auto_created=True,
('modified_at', models.DateTimeField(auto_now=True)), primary_key=True,
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='configs', to=settings.AUTH_USER_MODEL)), serialize=False,
verbose_name="ID",
),
),
("content", models.TextField(default="{}")),
("last_used_with_version", models.CharField(max_length=32, null=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("modified_at", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="configs",
to=settings.AUTH_USER_MODEL,
),
),
], ],
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='active_config', name="active_config",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='app.config'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="app.config",
),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='groups', name="groups",
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'), field=models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.Group",
verbose_name="groups",
),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='user_permissions', name="user_permissions",
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), field=models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.Permission",
verbose_name="user permissions",
),
), ),
] ]

View File

@ -6,18 +6,26 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('app', '0001_initial'), ("app", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Gateway', name="Gateway",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('host', models.CharField(max_length=255)), "id",
('port', models.IntegerField(default=1234)), models.BigAutoField(
('enabled', models.BooleanField(default=True)), auto_created=True,
('secure', models.BooleanField(default=True)), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("host", models.CharField(max_length=255)),
("port", models.IntegerField(default=1234)),
("enabled", models.BooleanField(default=True)),
("secure", models.BooleanField(default=True)),
], ],
), ),
] ]

View File

@ -7,18 +7,23 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('app', '0002_gateway'), ("app", "0002_gateway"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='gateway', model_name="gateway",
name='admin_port', name="admin_port",
field=models.IntegerField(default=1235), field=models.IntegerField(default=1235),
), ),
migrations.AlterField( migrations.AlterField(
model_name='user', model_name="user",
name='active_config', name="active_config",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='app.config'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="app.config",
),
), ),
] ]

View File

@ -3,7 +3,7 @@ from django.db import migrations, models
def run_forward(apps, schema_editor): def run_forward(apps, schema_editor):
for user in apps.get_model('app', 'User').objects.all(): for user in apps.get_model("app", "User").objects.all():
user.config_sync_token = secrets.token_hex(64) user.config_sync_token = secrets.token_hex(64)
user.save() user.save()
@ -11,19 +11,19 @@ def run_forward(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('app', '0003_auto_20210711_1855'), ("app", "0003_auto_20210711_1855"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='config_sync_token', name="config_sync_token",
field=models.CharField(blank=True, max_length=255, null=True), field=models.CharField(blank=True, max_length=255, null=True),
), ),
migrations.RunPython(run_forward, lambda _, __: None), migrations.RunPython(run_forward, lambda _, __: None),
migrations.AlterField( migrations.AlterField(
model_name='user', model_name="user",
name='config_sync_token', name="config_sync_token",
field=models.CharField(max_length=255), field=models.CharField(max_length=255),
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('app', '0004_sync_token'), ("app", "0004_sync_token"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='force_pro', name="force_pro",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
] ]

View File

@ -2,27 +2,27 @@ from django.db import migrations, models
def run_forward(apps, schema_editor): def run_forward(apps, schema_editor):
for config in apps.get_model('app', 'Config').objects.all(): for config in apps.get_model("app", "Config").objects.all():
config.name = f'Unnamed config ({config.created_at.date()})' config.name = f"Unnamed config ({config.created_at.date()})"
config.save() config.save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('app', '0005_user_force_pro'), ("app", "0005_user_force_pro"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='config', model_name="config",
name='name', name="name",
field=models.CharField(max_length=255, null=True), field=models.CharField(max_length=255, null=True),
), ),
migrations.RunPython(run_forward, lambda _, __: None), migrations.RunPython(run_forward, lambda _, __: None),
migrations.AlterField( migrations.AlterField(
model_name='config', model_name="config",
name='name', name="name",
field=models.CharField(max_length=255), field=models.CharField(max_length=255),
), ),
] ]

View File

@ -5,24 +5,30 @@ from django.contrib.auth.models import AbstractUser
class Config(models.Model): class Config(models.Model):
user = models.ForeignKey('app.User', related_name='configs', on_delete=models.CASCADE) user = models.ForeignKey(
"app.User", related_name="configs", on_delete=models.CASCADE
)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
content = models.TextField(default='{}') content = models.TextField(default="{}")
last_used_with_version = models.CharField(max_length=32, null=True) last_used_with_version = models.CharField(max_length=32, null=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True) modified_at = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.name: if not self.name:
self.name = f'Unnamed config ({date.today()})' self.name = f"Unnamed config ({date.today()})"
super().save(*args, **kwargs) super().save(*args, **kwargs)
class User(AbstractUser): class User(AbstractUser):
active_config = models.ForeignKey(Config, null=True, on_delete=models.SET_NULL, related_name='+') active_config = models.ForeignKey(
Config, null=True, on_delete=models.SET_NULL, related_name="+"
)
active_version = models.CharField(max_length=32, null=True) active_version = models.CharField(max_length=32, null=True)
custom_connection_gateway = models.CharField(max_length=255, null=True, blank=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) custom_connection_gateway_token = models.CharField(
max_length=255, null=True, blank=True
)
config_sync_token = models.CharField(max_length=255) config_sync_token = models.CharField(max_length=255)
force_pro = models.BooleanField(default=False) force_pro = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
@ -42,4 +48,4 @@ class Gateway(models.Model):
secure = models.BooleanField(default=True) secure = models.BooleanField(default=True)
def __str__(self): def __str__(self):
return f'{self.host}:{self.port}' return f"{self.host}:{self.port}"

View File

@ -7,13 +7,13 @@ from social_django.models import UserSocialAuth
from .models import User from .models import User
GQL_ENDPOINT = 'https://api.github.com/graphql' GQL_ENDPOINT = "https://api.github.com/graphql"
CACHE_KEY = 'cached-sponsors:%s' CACHE_KEY = "cached-sponsors:%s"
def check_is_sponsor(user: User) -> bool: def check_is_sponsor(user: User) -> bool:
try: try:
token = user.social_auth.get(provider='github').extra_data.get('access_token') token = user.social_auth.get(provider="github").extra_data.get("access_token")
except UserSocialAuth.DoesNotExist: except UserSocialAuth.DoesNotExist:
return False return False
@ -25,19 +25,19 @@ def check_is_sponsor(user: User) -> bool:
url=GQL_ENDPOINT, url=GQL_ENDPOINT,
use_json=True, use_json=True,
headers={ headers={
'Authorization': f'Bearer {token}', "Authorization": f"Bearer {token}",
} },
) )
) )
after = None after = None
while True: while True:
params = 'first: 1' params = "first: 1"
if after: if after:
params += f', after:"{after}"' params += f', after:"{after}"'
query = ''' query = """
query { query {
viewer { viewer {
sponsorshipsAsSponsor(%s) { sponsorshipsAsSponsor(%s) {
@ -56,18 +56,26 @@ def check_is_sponsor(user: User) -> bool:
} }
} }
} }
''' % (params,) """ % (
params,
)
response = client.execute(gql(query)) response = client.execute(gql(query))
info = response['viewer']['sponsorshipsAsSponsor'] info = response["viewer"]["sponsorshipsAsSponsor"]
after = info['pageInfo']['endCursor'] after = info["pageInfo"]["endCursor"]
nodes = info['nodes'] nodes = info["nodes"]
if not len(nodes): if not len(nodes):
break break
for node in nodes: for node in nodes:
if node['sponsorable']['login'].lower() not in settings.GITHUB_ELIGIBLE_SPONSORSHIPS: if (
node["sponsorable"]["login"].lower()
not in settings.GITHUB_ELIGIBLE_SPONSORSHIPS
):
continue continue
if info['totalRecurringMonthlyPriceInDollars'] >= settings.GITHUB_SPONSORS_MIN_PAYMENT: if (
info["totalRecurringMonthlyPriceInDollars"]
>= settings.GITHUB_SPONSORS_MIN_PAYMENT
):
return True return True
return False return False

View File

@ -7,12 +7,10 @@ from . import views
urlpatterns = [ urlpatterns = [
*[ *[
path(p, views.IndexView.as_view()) path(p, views.IndexView.as_view())
for p in ['', 'login', 'app', 'about', 'about/features'] for p in ["", "login", "app", "about", "about/features"]
], ],
path("app-dist/<version>/<path:path>", views.AppDistView.as_view()),
path('app-dist/<version>/<path:path>', views.AppDistView.as_view()), path("terminal", views.TerminalView.as_view()),
path('terminal', views.TerminalView.as_view()), path("demo", views.DemoView.as_view()),
path('demo', views.DemoView.as_view()), path("", include(api.urlpatterns)),
path('', include(api.urlpatterns)),
] ]

View File

@ -2,7 +2,11 @@ import fsspec
import os import os
from fsspec.implementations.local import LocalFileSystem from fsspec.implementations.local import LocalFileSystem
from django.conf import settings from django.conf import settings
from django.http.response import FileResponse, HttpResponseNotFound, HttpResponseRedirect from django.http.response import (
FileResponse,
HttpResponseNotFound,
HttpResponseRedirect,
)
from django.views import static from django.views import static
from rest_framework.views import APIView from rest_framework.views import APIView
from urllib.parse import urlparse from urllib.parse import urlparse
@ -12,27 +16,33 @@ class IndexView(APIView):
def get(self, request, format=None): def get(self, request, format=None):
if settings.FRONTEND_URL: if settings.FRONTEND_URL:
return HttpResponseRedirect(settings.FRONTEND_URL) return HttpResponseRedirect(settings.FRONTEND_URL)
return static.serve(request, 'index.html', document_root=str(settings.STATIC_ROOT)) return static.serve(
request, "index.html", document_root=str(settings.STATIC_ROOT)
)
class TerminalView(APIView): class TerminalView(APIView):
def get(self, request, format=None): def get(self, request, format=None):
response = static.serve(request, 'terminal.html', document_root=str(settings.STATIC_ROOT)) response = static.serve(
response['X-Frame-Options'] = 'SAMEORIGIN' request, "terminal.html", document_root=str(settings.STATIC_ROOT)
)
response["X-Frame-Options"] = "SAMEORIGIN"
return response return response
class DemoView(APIView): class DemoView(APIView):
def get(self, request, format=None): def get(self, request, format=None):
response = static.serve(request, 'demo.html', document_root=str(settings.STATIC_ROOT)) response = static.serve(
response['Content-Security-Policy'] = 'frame-ancestors https://tabby.sh' request, "demo.html", document_root=str(settings.STATIC_ROOT)
)
response["Content-Security-Policy"] = "frame-ancestors https://tabby.sh"
return response return response
class AppDistView(APIView): class AppDistView(APIView):
def get(self, request, version=None, path=None, format=None): def get(self, request, version=None, path=None, format=None):
fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme) fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme)
url = f'{settings.APP_DIST_STORAGE}/{version}/{path}' url = f"{settings.APP_DIST_STORAGE}/{version}/{path}"
if isinstance(fs, LocalFileSystem): if isinstance(fs, LocalFileSystem):
if not fs.exists(url): if not fs.exists(url):
return HttpResponseNotFound() return HttpResponseNotFound()

View File

@ -13,20 +13,20 @@ class BaseMiddleware:
class TokenMiddleware(BaseMiddleware): class TokenMiddleware(BaseMiddleware):
def __call__(self, request): def __call__(self, request):
token_value = None token_value = None
if 'auth_token' in request.GET: if "auth_token" in request.GET:
token_value = request.GET['auth_token'] token_value = request.GET["auth_token"]
if request.META.get('HTTP_AUTHORIZATION'): if request.META.get("HTTP_AUTHORIZATION"):
token_type, *credentials = request.META['HTTP_AUTHORIZATION'].split() token_type, *credentials = request.META["HTTP_AUTHORIZATION"].split()
if token_type == 'Bearer' and len(credentials): if token_type == "Bearer" and len(credentials):
token_value = credentials[0] token_value = credentials[0]
user = User.objects.filter(config_sync_token=token_value).first() user = User.objects.filter(config_sync_token=token_value).first()
if user: if user:
request.session.save = lambda *args, **kwargs: None request.session.save = lambda *args, **kwargs: None
setattr(user, 'backend', 'django.contrib.auth.backends.ModelBackend') setattr(user, "backend", "django.contrib.auth.backends.ModelBackend")
login(request, user) login(request, user)
setattr(request, '_dont_enforce_csrf_checks', True) setattr(request, "_dont_enforce_csrf_checks", True)
response = self.get_response(request) response = self.get_response(request)
@ -44,7 +44,7 @@ class GAMiddleware(BaseMiddleware):
def __call__(self, request): def __call__(self, request):
response = self.get_response(request) response = self.get_response(request)
if settings.GA_ID and request.path in ['/', '/app']: if settings.GA_ID and request.path in ["/", "/app"]:
try: try:
self.tracker.track_pageview(Page(request.path), Session(), Visitor()) self.tracker.track_pageview(Page(request.path), Session(), Visitor())
except Exception: except Exception:

View File

@ -9,99 +9,95 @@ load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'django-insecure') SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "django-insecure")
DEBUG = bool(os.getenv('DEBUG', False)) DEBUG = bool(os.getenv("DEBUG", False))
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ["*"]
USE_X_FORWARDED_HOST = True USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'rest_framework', "rest_framework",
'social_django', "social_django",
'corsheaders', "corsheaders",
'tabby.app', "tabby.app",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'whitenoise.middleware.WhiteNoiseMiddleware', "whitenoise.middleware.WhiteNoiseMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
'corsheaders.middleware.CorsMiddleware', "corsheaders.middleware.CorsMiddleware",
'tabby.middleware.TokenMiddleware', "tabby.middleware.TokenMiddleware",
'tabby.middleware.GAMiddleware', "tabby.middleware.GAMiddleware",
] ]
ROOT_URLCONF = 'tabby.urls' ROOT_URLCONF = "tabby.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'tabby.wsgi.application' WSGI_APPLICATION = "tabby.wsgi.application"
DATABASES = { DATABASES = {"default": dj_database_url.config(conn_max_age=600)}
'default': dj_database_url.config(conn_max_age=600)
}
CACHES = { CACHES = {
'default': { "default": {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
} }
} }
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}, },
] ]
AUTH_USER_MODEL = 'app.User' AUTH_USER_MODEL = "app.User"
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",)
'rest_framework.renderers.JSONRenderer',
)
} }
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = "en-us"
TIME_ZONE = 'UTC' TIME_ZONE = "UTC"
USE_I18N = True USE_I18N = True
@ -110,144 +106,144 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
LOGGING = { LOGGING = {
'version': 1, "version": 1,
'disable_existing_loggers': False, "disable_existing_loggers": False,
'formatters': { "formatters": {
'simple': { "simple": {"format": "%(levelname)s %(message)s"},
'format': '%(levelname)s %(message)s' },
"handlers": {
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "simple",
}, },
}, },
'handlers': { "loggers": {
'console': { "": {
'level': 'INFO', "handlers": ["console"],
'class': 'logging.StreamHandler', "propagate": False,
'formatter': 'simple' "level": "INFO",
},
},
'loggers': {
'': {
'handlers': ['console'],
'propagate': False,
'level': 'INFO',
}, },
}, },
} }
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
CSRF_USE_SESSIONS = False CSRF_USE_SESSIONS = False
CSRF_COOKIE_HTTPONLY = False CSRF_COOKIE_HTTPONLY = False
CSRF_COOKIE_NAME = 'XSRF-TOKEN' CSRF_COOKIE_NAME = "XSRF-TOKEN"
CSRF_HEADER_NAME = 'HTTP_X_XSRF_TOKEN' CSRF_HEADER_NAME = "HTTP_X_XSRF_TOKEN"
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'social_core.backends.github.GithubOAuth2', "social_core.backends.github.GithubOAuth2",
'social_core.backends.gitlab.GitLabOAuth2', "social_core.backends.gitlab.GitLabOAuth2",
'social_core.backends.azuread.AzureADOAuth2', "social_core.backends.azuread.AzureADOAuth2",
'social_core.backends.microsoft.MicrosoftOAuth2', "social_core.backends.microsoft.MicrosoftOAuth2",
'social_core.backends.google.GoogleOAuth2', "social_core.backends.google.GoogleOAuth2",
'django.contrib.auth.backends.ModelBackend', "django.contrib.auth.backends.ModelBackend",
) )
SOCIAL_AUTH_GITHUB_SCOPE = ['read:user', 'user:email'] SOCIAL_AUTH_GITHUB_SCOPE = ["read:user", "user:email"]
SOCIAL_AUTH_PIPELINE = ( SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details', "social_core.pipeline.social_auth.social_details",
'social_core.pipeline.social_auth.social_uid', "social_core.pipeline.social_auth.social_uid",
'social_core.pipeline.social_auth.auth_allowed', "social_core.pipeline.social_auth.auth_allowed",
'social_core.pipeline.social_auth.social_user', "social_core.pipeline.social_auth.social_user",
'social_core.pipeline.user.get_username', "social_core.pipeline.user.get_username",
'social_core.pipeline.social_auth.associate_by_email', "social_core.pipeline.social_auth.associate_by_email",
'social_core.pipeline.user.create_user', "social_core.pipeline.user.create_user",
'social_core.pipeline.social_auth.associate_user', "social_core.pipeline.social_auth.associate_user",
'social_core.pipeline.social_auth.load_extra_data', "social_core.pipeline.social_auth.load_extra_data",
'social_core.pipeline.user.user_details', "social_core.pipeline.user.user_details",
) )
APP_DIST_STORAGE = os.getenv('APP_DIST_STORAGE', 'file://' + str(BASE_DIR / 'app-dist')) APP_DIST_STORAGE = os.getenv("APP_DIST_STORAGE", "file://" + str(BASE_DIR / "app-dist"))
NPM_REGISTRY = os.getenv('NPM_REGISTRY', 'https://registry.npmjs.org').rstrip('/') NPM_REGISTRY = os.getenv("NPM_REGISTRY", "https://registry.npmjs.org").rstrip("/")
FRONTEND_BUILD_DIR = Path(os.getenv('FRONTEND_BUILD_DIR', BASE_DIR / '../frontend/build')) FRONTEND_BUILD_DIR = Path(
os.getenv("FRONTEND_BUILD_DIR", BASE_DIR / "../frontend/build")
)
FRONTEND_URL = None FRONTEND_URL = None
BACKEND_URL = None BACKEND_URL = None
GITHUB_ELIGIBLE_SPONSORSHIPS = None GITHUB_ELIGIBLE_SPONSORSHIPS = None
for key in [ for key in [
'FRONTEND_URL', "FRONTEND_URL",
'BACKEND_URL', "BACKEND_URL",
'SOCIAL_AUTH_GITHUB_KEY', "SOCIAL_AUTH_GITHUB_KEY",
'SOCIAL_AUTH_GITHUB_SECRET', "SOCIAL_AUTH_GITHUB_SECRET",
'SOCIAL_AUTH_GITLAB_KEY', "SOCIAL_AUTH_GITLAB_KEY",
'SOCIAL_AUTH_GITLAB_SECRET', "SOCIAL_AUTH_GITLAB_SECRET",
'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', "SOCIAL_AUTH_GOOGLE_OAUTH2_KEY",
'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET', "SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET",
'SOCIAL_AUTH_MICROSOFT_GRAPH_KEY', "SOCIAL_AUTH_MICROSOFT_GRAPH_KEY",
'SOCIAL_AUTH_MICROSOFT_GRAPH_SECRET', "SOCIAL_AUTH_MICROSOFT_GRAPH_SECRET",
'CONNECTION_GATEWAY_AUTH_CA', "CONNECTION_GATEWAY_AUTH_CA",
'CONNECTION_GATEWAY_AUTH_CERTIFICATE', "CONNECTION_GATEWAY_AUTH_CERTIFICATE",
'CONNECTION_GATEWAY_AUTH_KEY', "CONNECTION_GATEWAY_AUTH_KEY",
'GITHUB_ELIGIBLE_SPONSORSHIPS', "GITHUB_ELIGIBLE_SPONSORSHIPS",
'GITHUB_SPONSORS_MIN_PAYMENT', "GITHUB_SPONSORS_MIN_PAYMENT",
'GA_ID', "GA_ID",
'GA_DOMAIN', "GA_DOMAIN",
]: ]:
globals()[key] = os.getenv(key) globals()[key] = os.getenv(key)
for key in [ for key in [
'GITHUB_SPONSORS_MIN_PAYMENT', "GITHUB_SPONSORS_MIN_PAYMENT",
]: ]:
globals()[key] = int(globals()[key]) if globals()[key] else None globals()[key] = int(globals()[key]) if globals()[key] else None
for key in [ for key in [
'CONNECTION_GATEWAY_AUTH_CA', "CONNECTION_GATEWAY_AUTH_CA",
'CONNECTION_GATEWAY_AUTH_CERTIFICATE', "CONNECTION_GATEWAY_AUTH_CERTIFICATE",
'CONNECTION_GATEWAY_AUTH_KEY', "CONNECTION_GATEWAY_AUTH_KEY",
]: ]:
v = globals()[key] v = globals()[key]
if v and not os.path.exists(v): if v and not os.path.exists(v):
raise ValueError(f'{v} does not exist') raise ValueError(f"{v} does not exist")
if GITHUB_ELIGIBLE_SPONSORSHIPS: if GITHUB_ELIGIBLE_SPONSORSHIPS:
GITHUB_ELIGIBLE_SPONSORSHIPS = GITHUB_ELIGIBLE_SPONSORSHIPS.split(',') GITHUB_ELIGIBLE_SPONSORSHIPS = GITHUB_ELIGIBLE_SPONSORSHIPS.split(",")
else: else:
GITHUB_ELIGIBLE_SPONSORSHIPS = [] GITHUB_ELIGIBLE_SPONSORSHIPS = []
STATIC_URL = '/static/' STATIC_URL = "/static/"
if FRONTEND_BUILD_DIR.exists(): if FRONTEND_BUILD_DIR.exists():
STATICFILES_DIRS = [FRONTEND_BUILD_DIR] STATICFILES_DIRS = [FRONTEND_BUILD_DIR]
STATIC_ROOT = BASE_DIR / 'public' STATIC_ROOT = BASE_DIR / "public"
if FRONTEND_URL: if FRONTEND_URL:
CORS_ALLOWED_ORIGINS = [FRONTEND_URL, 'https://tabby.sh'] CORS_ALLOWED_ORIGINS = [FRONTEND_URL, "https://tabby.sh"]
CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_HEADERS = [ CORS_ALLOW_HEADERS = [
'accept', "accept",
'accept-encoding', "accept-encoding",
'authorization', "authorization",
'content-type', "content-type",
'dnt', "dnt",
'origin', "origin",
'user-agent', "user-agent",
'x-xsrf-token', "x-xsrf-token",
'x-requested-with', "x-requested-with",
] ]
frontend_domain = urlparse(FRONTEND_URL).hostname frontend_domain = urlparse(FRONTEND_URL).hostname
CSRF_TRUSTED_ORIGINS = [frontend_domain] CSRF_TRUSTED_ORIGINS = [frontend_domain]
if BACKEND_URL: if BACKEND_URL:
CSRF_TRUSTED_ORIGINS.append(urlparse(BACKEND_URL).hostname) CSRF_TRUSTED_ORIGINS.append(urlparse(BACKEND_URL).hostname)
SESSION_COOKIE_DOMAIN = os.getenv('SESSION_COOKIE_DOMAIN', frontend_domain) SESSION_COOKIE_DOMAIN = os.getenv("SESSION_COOKIE_DOMAIN", frontend_domain)
SESSION_COOKIE_SAMESITE = None SESSION_COOKIE_SAMESITE = None
CSRF_COOKIE_DOMAIN = frontend_domain CSRF_COOKIE_DOMAIN = frontend_domain
FRONTEND_URL = FRONTEND_URL.rstrip('/') FRONTEND_URL = FRONTEND_URL.rstrip("/")
if FRONTEND_URL.startswith('https://'): if FRONTEND_URL.startswith("https://"):
CSRF_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True
else: else:
FRONTEND_URL = '' FRONTEND_URL = ""
LOGIN_REDIRECT_URL = FRONTEND_URL + '/app' LOGIN_REDIRECT_URL = FRONTEND_URL + "/app"

View File

@ -4,7 +4,7 @@ from django.urls import path, include
from .app.urls import urlpatterns as app_urlpatterns from .app.urls import urlpatterns as app_urlpatterns
urlpatterns = [ urlpatterns = [
path('', include(app_urlpatterns)), path("", include(app_urlpatterns)),
path('api/1/auth/social/', include('social_django.urls', namespace='social')), path("api/1/auth/social/", include("social_django.urls", namespace="social")),
path('admin/', admin.site.urls), path("admin/", admin.site.urls),
] ]

View File

@ -2,6 +2,6 @@ import os
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tabby.settings') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tabby.settings")
application = get_wsgi_application() application = get_wsgi_application()