Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 25 additions & 2 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,34 @@ GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GITHUB_REPO_TO_STAR=genlayerlabs/genlayer-project-boilerplate

# GitHub OAuth Token Encryption Key
# This key encrypts GitHub access tokens before storing them in the database.
# Social Connections Encryption Key
# This key encrypts OAuth access tokens before storing them in the database.
# IMPORTANT: This key must remain constant - changing it will make all stored tokens unreadable.
# Generate a new key with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# The key is a base64-encoded 32-byte value (e.g., "07NNDpobC9R40oJnBxwvb6ifUENNGJTiaz3E562SgVw=")
# Note: Falls back to GITHUB_ENCRYPTION_KEY if not set (for backward compatibility)
SOCIAL_ENCRYPTION_KEY=your_encryption_key_here

# Twitter/X OAuth 2.0 Configuration
# Create a Twitter OAuth App at https://developer.twitter.com/en/portal/dashboard
# 1. Create a project/app and enable OAuth 2.0
# 2. Set the callback URL to: {BACKEND_URL}/api/auth/twitter/callback/
# 3. Enable scopes: tweet.read, users.read, offline.access
TWITTER_CLIENT_ID=your_twitter_client_id
TWITTER_CLIENT_SECRET=your_twitter_client_secret

# Discord OAuth 2.0 Configuration
# Create a Discord OAuth App at https://discord.com/developers/applications
# 1. Create a new application
# 2. Go to OAuth2 > General and add redirect URL: {BACKEND_URL}/api/auth/discord/callback/
# 3. Required scopes: identify, guilds
DISCORD_CLIENT_ID=your_discord_client_id
DISCORD_CLIENT_SECRET=your_discord_client_secret
# Discord Guild (Server) ID for membership checks
# To get: Enable Developer Mode in Discord, right-click server > Copy Server ID
DISCORD_GUILD_ID=your_discord_server_id

# Legacy: GitHub OAuth Token Encryption Key (deprecated, use SOCIAL_ENCRYPTION_KEY)
GITHUB_ENCRYPTION_KEY=your_encryption_key_here

# Google reCAPTCHA Configuration
Expand Down
1 change: 1 addition & 0 deletions backend/social_connections/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'social_connections.apps.SocialConnectionsConfig'
4 changes: 4 additions & 0 deletions backend/social_connections/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Admin registrations are handled in each sub-app:
# - social_connections.github.admin
# - social_connections.twitter.admin
# - social_connections.discord.admin
7 changes: 7 additions & 0 deletions backend/social_connections/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig


class SocialConnectionsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'social_connections'
verbose_name = 'Social Connections'
1 change: 1 addition & 0 deletions backend/social_connections/discord/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'social_connections.discord.apps.DiscordConfig'
25 changes: 25 additions & 0 deletions backend/social_connections/discord/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from django.contrib import admin
from .models import DiscordConnection


@admin.register(DiscordConnection)
class DiscordConnectionAdmin(admin.ModelAdmin):
list_display = ['user', 'username', 'discriminator', 'platform_user_id', 'linked_at', 'created_at']
list_filter = ['linked_at']
search_fields = ['user__email', 'user__name', 'username', 'platform_user_id']
readonly_fields = ['platform_user_id', 'access_token', 'refresh_token', 'avatar_hash', 'linked_at', 'created_at', 'updated_at']
raw_id_fields = ['user']

fieldsets = (
(None, {
'fields': ('user', 'username', 'discriminator', 'platform_user_id')
}),
('Avatar', {
'fields': ('avatar_hash',),
'classes': ('collapse',)
}),
('Timestamps', {
'fields': ('linked_at', 'created_at', 'updated_at'),
'classes': ('collapse',)
}),
)
8 changes: 8 additions & 0 deletions backend/social_connections/discord/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.apps import AppConfig


class DiscordConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'social_connections.discord'
label = 'social_discord'
verbose_name = 'Discord Connection'
37 changes: 37 additions & 0 deletions backend/social_connections/discord/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 5.2.7 on 2026-01-30 15:31

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='DiscordConnection',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('username', models.CharField(blank=True, help_text='Platform username', max_length=100)),
('platform_user_id', models.CharField(blank=True, help_text='Platform user ID for unique identification', max_length=50)),
('access_token', models.TextField(blank=True, help_text='Encrypted access token')),
('refresh_token', models.TextField(blank=True, help_text='Encrypted refresh token (if applicable)')),
('linked_at', models.DateTimeField(blank=True, help_text='When the account was linked', null=True)),
('discriminator', models.CharField(blank=True, help_text='Discord discriminator (legacy - newer accounts may not have this)', max_length=10)),
('avatar_hash', models.CharField(blank=True, help_text='Discord avatar hash for constructing avatar URL', max_length=100)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='discord_connection', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Discord Connection',
'verbose_name_plural': 'Discord Connections',
},
),
]
Empty file.
40 changes: 40 additions & 0 deletions backend/social_connections/discord/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
Discord OAuth connection model.
"""
from django.db import models
from social_connections.models import SocialConnection


class DiscordConnection(SocialConnection):
"""
Discord OAuth connection.

Stores the user's Discord account information obtained through OAuth 2.0.
Includes Discord-specific fields like discriminator and avatar hash.
"""
user = models.OneToOneField(
'users.User',
on_delete=models.CASCADE,
related_name='discord_connection'
)
discriminator = models.CharField(
max_length=10,
blank=True,
help_text="Discord discriminator (legacy - newer accounts may not have this)"
)
avatar_hash = models.CharField(
max_length=100,
blank=True,
help_text="Discord avatar hash for constructing avatar URL"
)

class Meta:
verbose_name = 'Discord Connection'
verbose_name_plural = 'Discord Connections'

@property
def avatar_url(self):
"""Construct the Discord avatar URL from the avatar hash."""
if not self.avatar_hash or not self.platform_user_id:
return None
return f"https://cdn.discordapp.com/avatars/{self.platform_user_id}/{self.avatar_hash}.png"
Loading