Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
40 changes: 40 additions & 0 deletions .github/workflows/telegram-deliver-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Deliver Telegram Notifications (dev)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Both Telegram delivery workflows lack explicit permissions: and concurrency: blocks. Root cause: neither workflow was scaffolded with a hardening block, so both default to broad token permissions and allow overlapping scheduled runs.

  • .github/workflows/telegram-deliver-dev.yml#L3-13: add a top-level permissions: contents: read and a concurrency: {group: telegram-deliver-dev, cancel-in-progress: false} block.
  • .github/workflows/telegram-deliver.yml#L3-12: add a top-level permissions: contents: read and a concurrency: {group: telegram-deliver-prod, cancel-in-progress: false} block.
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 1-34: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/telegram-deliver-dev.yml at line 1, Add top-level
permissions and concurrency hardening blocks to both Telegram delivery
workflows: grant only contents: read, and define non-canceling concurrency
groups telegram-deliver-dev for the development workflow and
telegram-deliver-prod for the production workflow.

Source: Linters/SAST tools


permissions:
contents: read

concurrency:
group: telegram-deliver-dev
cancel-in-progress: false

on:
schedule:
# GitHub Actions schedules are best-effort; avoid crowded :00/:15/:30/:45 slots.
# Start at :01 so the last slot (:56) rolls to :01 with no hourly gap.
- cron: '1-59/5 * * * *'
workflow_dispatch:

jobs:
deliver:
runs-on: ubuntu-latest
environment: cron-job
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd 'deploy-apprunner' backend --exec cat {}
fd '.env.example' backend --exec grep -n -i 'CRON_SYNC_TOKEN' {}

Repository: genlayer-foundation/points

Length of output: 37287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in .github/workflows/telegram-deliver-dev.yml .github/workflows/telegram-deliver.yml; do
  echo "===== $f ====="
  cat -n "$f" | sed -n '1,200p'
  echo
done

echo "===== CRON_SYNC_TOKEN references ====="
rg -n "CRON_SYNC_TOKEN|environment:" .github/workflows -S

Repository: genlayer-foundation/points

Length of output: 4579


Split cron delivery into separate environments

Both workflows use environment: cron-job, so secrets.CRON_SYNC_TOKEN comes from the same secret scope in dev and prod. Use separate environments (for example cron-job-dev / cron-job-prod) or distinct secret names if the tokens should differ.

📍 Affects 2 files
  • .github/workflows/telegram-deliver-dev.yml#L13-L13 (this comment)
  • .github/workflows/telegram-deliver.yml#L12-L12
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/telegram-deliver-dev.yml at line 13, Separate the cron
delivery secret scopes by updating the environment declarations in
.github/workflows/telegram-deliver-dev.yml (line 13) and
.github/workflows/telegram-deliver.yml (line 12) to distinct environment names
such as cron-job-dev and cron-job-prod, while keeping the existing
CRON_SYNC_TOKEN references intact.

steps:
- name: Drain Telegram outbox (dev)
run: |
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Content-Type: application/json" \
-H "X-Cron-Token: ${{ secrets.CRON_SYNC_TOKEN }}" \
"${{ secrets.DEV_API_BASE_URL }}/api/v1/notifications/telegram/deliver/")

http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')

echo "Response: $body"
echo "HTTP Code: $http_code"

if [ "$http_code" = "200" ]; then
echo "Telegram delivery run completed"
else
echo "Telegram delivery failed with status $http_code"
exit 1
fi
39 changes: 39 additions & 0 deletions .github/workflows/telegram-deliver.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Deliver Telegram Notifications

permissions:
contents: read

concurrency:
group: telegram-deliver-prod
cancel-in-progress: false

on:
schedule:
# GitHub Actions schedules are best-effort; avoid crowded :00/:15/:30/:45 slots.
- cron: '4-59/5 * * * *'
workflow_dispatch:

jobs:
deliver:
runs-on: ubuntu-latest
environment: cron-job
Comment thread
coderabbitai[bot] marked this conversation as resolved.
steps:
- name: Drain Telegram outbox
run: |
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Content-Type: application/json" \
-H "X-Cron-Token: ${{ secrets.CRON_SYNC_TOKEN }}" \
"${{ secrets.API_BASE_URL }}/api/v1/notifications/telegram/deliver/")

http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')

echo "Response: $body"
echo "HTTP Code: $http_code"

if [ "$http_code" = "200" ]; then
echo "Telegram delivery run completed"
else
echo "Telegram delivery failed with status $http_code"
exit 1
fi
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable user-facing changes to this project will be documented in this file.

## Unreleased

- Contributors can link a Telegram account from their profile header (private, visible only to them) to receive portal notifications and announcements on Telegram and check their rank, points, and active missions with bot commands; the old public Telegram handle field was removed from profiles. Post-deploy: set TELEGRAM_BOT_USERNAME and TELEGRAM_WEBHOOK_SECRET, run `python manage.py set_telegram_webhook`, and add the telegram-deliver cron workflow (de6a8726)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- Finishing the Creator or Builder journey now actually grants the role: since late June the final "Become a Creator" / "Claim Builder Role" step failed for every new member with a generic error, and completion errors now show their real reason instead of a dead-end "try again" (9d546e70)

- Validators can link Telegram support groups to their validator: generate a one-time code on the new Telegram Support page, paste it in a Telegram group with the Deckard support bot, and the group is bound to the validator (multiple groups supported, codes expire in 48 hours and can be revoked) (0cd7e5f)
Expand Down
8 changes: 7 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,15 @@ DEFILLAMA_FEES_RANK_URL=https://defillama.com/fees/chains
# JSON array of curated validators, for example:
# [{"name":"Validator","subtitle":"GenLayer validator","aum":"$42.6M","logo_url":"https://..."}]
OVERVIEW_TOP_VALIDATORS=
# Telegram portal bot (notifications + commands). Token from BotFather; the
# same bot also feeds the member-count metric below when it is in the group.
TELEGRAM_BOT_TOKEN=
# Bot username without @, used for t.me account-linking deep links.
TELEGRAM_BOT_USERNAME=
# Random secret Telegram echoes back on webhook calls (openssl rand -hex 32).
TELEGRAM_WEBHOOK_SECRET=
# Telegram members shown in the overview hero. Set bot token + chat id for the live count,
# otherwise the backend falls back to TELEGRAM_MEMBERS or 13300.
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
TELEGRAM_MEMBERS=13300
# GenLayer Studio executive-metrics dashboard (decisions/chain-tx time series for the overview chart)
Expand Down
29 changes: 23 additions & 6 deletions backend/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ backend/
├── api/ # Core API app
├── contributions/ # Contribution tracking
├── leaderboard/ # Leaderboard and rankings
<<<<<<< HEAD
├── social_connections/ # OAuth (GitHub, Twitter, Discord) + Telegram bot link + encrypted token storage
=======
├── social_connections/ # OAuth (GitHub, Twitter, Discord) + encrypted token storage + Telegram (bot-confirmed, no OAuth)
>>>>>>> origin/dev
├── social_tasks/ # Repeatable social tasks (follow, join, like) and completions
├── users/ # User management and auth
├── partners/ # Ecosystem partners directory
Expand All @@ -47,13 +51,13 @@ backend/
- Validator model with node_version field (OneToOne with User)
- Custom UserManager for email-based auth
- **Views**: `users/views.py`
- `/api/v1/users/me/` - GET/PATCH current user profile (name/description/website/socials editable; node_version is NOT editable — Grafana-sourced, display only)
- `/api/v1/users/me/` - GET/PATCH current user profile (name/description/website/linkedin editable; telegram_handle removed — Telegram links via the bot's verified TelegramConnection; node_version is NOT editable — Grafana-sourced, display only)
- `/api/v1/users/by-address/{address}/` - Get user by wallet address
- `/api/v1/users/validators/` - Get validator list from blockchain
- **Serializers**: `users/serializers.py`
- UserSerializer - Full user data including validator info
- ValidatorSerializer - Validator node version and target matching
- UserProfileUpdateSerializer - Allows name/description/website/socials updates (node_version removed — Grafana is source of truth)
- UserProfileUpdateSerializer - Allows name/description/website/linkedin updates (telegram_handle removed — replaced by the private TelegramConnection; node_version removed — Grafana is source of truth)
- UserCreateSerializer - Registration

### Authentication
Expand Down Expand Up @@ -331,9 +335,9 @@ backend/
- **Models**: `notifications/models.py`
- `Notification` - Personal (has `recipient`) or broadcast (`recipient=None` + `audience`: all/validators/stewards/builders/community). Audiences resolve via the role OneToOnes (Validator/Steward/Builder/Creator) in `services.audiences_for`. Broadcasts are ONE row regardless of user count; users see broadcasts created after their `date_joined`. Frozen copy (`title`/`body`/`link_url`), `payload` JSON for future channel renderers, `dedupe_key` (re-broadcasting a source object refreshes + resurfaces instead of duplicating).
- `NotificationReceipt` - Lazy per-user read state for broadcast rows (created on read).
- `CustomNotification` - Admin-composed campaign: title/markdown body/optional link + targeting (`everyone` | `roles` union of builders/validators/stewards/creators | hand-picked `target_users` M2M | pasted `target_wallets`) + delivery record (`status` draft/sent, `sent_count`, `unmatched_wallets`, `channels` reserved for email/Telegram).
- `CustomNotification` - Admin-composed campaign: title/markdown body/optional link + targeting (`everyone` | `roles` union of builders/validators/stewards/creators | hand-picked `target_users` M2M | pasted `target_wallets`) + delivery record (`status` draft/sent, `sent_count`, `unmatched_wallets`, `channels` JSON list: `portal` always + optional `telegram` checkbox in the admin form).
- **Campaigns**: `notifications/campaigns.py`
- `resolve_recipients(campaign)` - The channel-agnostic enumeration step (always `is_active=True`; banned/invisible users included by design). Future email/Telegram channels reuse this and add their own delivery.
- `resolve_recipients(campaign)` - The channel-agnostic enumeration step (always `is_active=True`; banned/invisible users included by design). The Telegram channel reuses this (`notifications/telegram.py:enqueue_campaign`); a future email channel would do the same.
- `send_campaign(campaign, actor=...)` - Fans out personal `Notification` rows (snapshot semantics, never broadcast rows, so campaigns stay private to recipients). Idempotent via dedupe key `custom.announcement:{pk}`; resend refreshes copy + resurfaces unread, scoped to the currently resolved audience.
- `recall_campaign(campaign)` - Deletes delivered portal notification rows for that campaign while keeping the campaign record for audit/resend.
- Compose flow: Django admin > Notifications > Custom notifications. Saving is a silent draft with reach preview; off-by-default "Send now" checkbox or `send_selected`/`resend_selected` actions deliver. "Recall delivered portal notifications" or the `recall_selected` action removes delivered portal rows. The send/recall runs in `save_related` (M2M targeting commits after `save_model`).
Expand All @@ -347,7 +351,9 @@ backend/
- `/api/v1/notifications/unread-count/` - Unread badge count
- `/api/v1/notifications/{id}/mark-read/` - Personal sets `read_at`; broadcast creates a receipt
- `/api/v1/notifications/mark-all-read/`
- **Future channels**: email/Telegram slot in via registry `channels` + a delivery outbox and `NotificationPreference` model when the first external channel ships (Telegram link would follow the `social_connections` pattern).
- **Telegram channel** (`notifications/telegram.py`): events whose registry entry includes `'telegram'` in `channels` (all `submission.*`, `contribution.highlighted`, `validator.graduated`, `mission.published`, `node_version.published`, `alert.published`, `custom.announcement`) also enqueue rows into the `social_connections.TelegramMessage` outbox for recipients with a linked, unmuted, unblocked `TelegramConnection`. `notify()` enqueues per recipient; `broadcast()` fans out via `services.users_for_audience(audience)` (the queryset twin of `estimate_broadcast_reach`); `send_campaign()` gates on `campaign.channels`. Enqueues are idempotent (partial unique constraint on `(notification, connection)` — re-broadcasts/resends only reach users who linked after the original send) and best-effort (failures never break notification creation). The outbox is drained by `POST /api/v1/notifications/telegram/deliver/` (IsCronToken; GitHub Action `telegram-deliver.yml` every 5 min; ~25 msg/s pacing, 3 attempts, 429-aware) or `python manage.py deliver_telegram_messages`. Campaign recall cancels pending outbox rows; already-sent Telegram messages cannot be recalled.
- **Telegram bot** (`social_connections/telegram_bot.py` + `telegram.py`): deep-link account linking (portal issues a one-time `PendingOAuthState` token; `https://t.me/<bot>?start=<token>`; the webhook binds the sender's numeric Telegram id to a `TelegramConnection`), webhook `POST /api/webhooks/telegram/` (validated via `X-Telegram-Bot-Api-Secret-Token` == `TELEGRAM_WEBHOOK_SECRET`, always 200 after auth), and one-shot commands: `/rank`, `/points` (LeaderboardEntry), `/missions` (active Missions), `/mute`/`/unmute` (`notifications_enabled`), `/unlink`, `/help`. All inbound/outbound messages are logged in `TelegramMessage`. **Privacy: TelegramConnection is owner-only** — `UserSerializer.get_telegram_connection` returns it for owner/staff only, `to_representation` strips `telegram_connection`/`telegram_handle` for other viewers, user search does not match `telegram_handle`, and `UserProfileUpdateSerializer` no longer accepts `telegram_handle` (replaced by the verified connection). Dev: `python manage.py run_telegram_polling` (getUpdates loop, no public URL needed; refuses to run while a webhook is registered unless `--delete-webhook` is passed, so a prod token can't be hijacked by accident); prod one-time setup: `python manage.py set_telegram_webhook --url https://<api-host>/api/webhooks/telegram/`. The three `TELEGRAM_*` env vars are wired into both App Runner deploy scripts as SSM secrets (`telegram_bot_token` / `telegram_bot_username` / `telegram_webhook_secret`) — create the SSM parameters BEFORE deploying or App Runner will fail to start.
- **Future channels**: email slots in the same way via registry `channels` + its own delivery module; per-user `NotificationPreference` is still future work (Telegram has a single `/mute`).

### Gen TV
- **Models**: `gen_tv/models.py`
Expand Down Expand Up @@ -542,10 +548,19 @@ GET /api/v1/notifications/ (requires auth, ?unread=true ?categor
GET /api/v1/notifications/unread-count/ (requires auth)
POST /api/v1/notifications/{id}/mark-read/ (requires auth)
POST /api/v1/notifications/mark-all-read/ (requires auth)
<<<<<<< HEAD
POST /api/v1/notifications/telegram/deliver/ (cron-protected, X-Cron-Token; drains the Telegram outbox)

# Telegram bot
POST /api/v1/users/telegram/link-token/ (requires auth; returns one-time t.me deep link)
POST /api/v1/users/telegram/disconnect/ (requires auth; idempotent unlink)
POST /api/webhooks/telegram/ (Telegram only; X-Telegram-Bot-Api-Secret-Token)
=======

# Campaign vanity links (public; the portal CDN passes /join/* through to the backend)
GET /join/{role}/{alias} (anonymous GET/HEAD, 302 with UTMs, throttled 120/min)
GET /campaigns/redirect/{role}/{alias} (same view; original internal path)
>>>>>>> origin/dev
```

### Leaderboard monthly date ranges
Expand Down Expand Up @@ -605,7 +620,9 @@ The cron `POST /api/v1/metrics/overview/refresh/` (GitHub Action `sync-overview-
- `OVERVIEW_TOP_VALIDATORS` - optional JSON array of curated validators; superseded by the per-wallet `ValidatorWallet.show_in_overview` + `assets_under_management_usd` admin fields when any are set.
- `DEFILLAMA_FEES_RANK` / `DEFILLAMA_FEES_RANK_URL` - the DeFiLlama fees-rank value/source shown on the overview.
- `DISCORD_BOT_TOKEN` + `DISCORD_GUILD_ID` (Discord members), `SORSA_API_KEY` + `X_METRICS_USERNAME` (X followers), `GITHUB_METRICS_REPO` + `GITHUB_METRICS_TOKEN` (boilerplate stars).
- `TELEGRAM_BOT_TOKEN` + `TELEGRAM_CHAT_ID` for the live Telegram member count, else `TELEGRAM_MEMBERS` or the built-in `13300` curated fallback.
- `TELEGRAM_BOT_TOKEN` + `TELEGRAM_CHAT_ID` for the live Telegram member count, else `TELEGRAM_MEMBERS` or the built-in `13300` curated fallback. The same token powers the portal Telegram bot (notifications + commands); the bot must be a member of the chat for the count to work.
- `TELEGRAM_BOT_USERNAME` - Portal bot username without `@`; used to build `t.me/<bot>?start=<token>` account-linking deep links (link-token endpoint returns 503 when unset).
- `TELEGRAM_WEBHOOK_SECRET` - Random secret registered via `set_telegram_webhook`; Telegram echoes it in `X-Telegram-Bot-Api-Secret-Token` and the webhook rejects everything when it is unset or mismatched.

**AWS Deployment:** For production deployments on AWS App Runner, all environment variables must be stored in AWS Systems Manager (SSM) Parameter Store. See `aws-deployment-guide.md` for setup instructions.

Expand Down
6 changes: 6 additions & 0 deletions backend/deploy-apprunner-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ if aws apprunner describe-service --service-arn arn:aws:apprunner:$REGION:$ACCOU
"DEFILLAMA_FEES_RANK": "$SSM_PREFIX/$SSM_ENV/defillama_fees_rank",
"DEFILLAMA_FEES_RANK_URL": "$SSM_PREFIX/$SSM_ENV/defillama_fees_rank_url",
"TELEGRAM_MEMBERS": "$SSM_PREFIX/$SSM_ENV/telegram_members",
"TELEGRAM_BOT_TOKEN": "$SSM_PREFIX/$SSM_ENV/telegram_bot_token",
"TELEGRAM_BOT_USERNAME": "$SSM_PREFIX/$SSM_ENV/telegram_bot_username",
"TELEGRAM_WEBHOOK_SECRET": "$SSM_PREFIX/$SSM_ENV/telegram_webhook_secret",
"SOCIAL_ENCRYPTION_KEY": "$SSM_PREFIX/$SSM_ENV/social_encryption_key",
"TWITTER_CLIENT_ID": "$SSM_PREFIX/$SSM_ENV/twitter_client_id",
"TWITTER_CLIENT_SECRET": "$SSM_PREFIX/$SSM_ENV/twitter_client_secret",
Expand Down Expand Up @@ -291,6 +294,9 @@ EOF
"DEFILLAMA_FEES_RANK": "$SSM_PREFIX/$SSM_ENV/defillama_fees_rank",
"DEFILLAMA_FEES_RANK_URL": "$SSM_PREFIX/$SSM_ENV/defillama_fees_rank_url",
"TELEGRAM_MEMBERS": "$SSM_PREFIX/$SSM_ENV/telegram_members",
"TELEGRAM_BOT_TOKEN": "$SSM_PREFIX/$SSM_ENV/telegram_bot_token",
"TELEGRAM_BOT_USERNAME": "$SSM_PREFIX/$SSM_ENV/telegram_bot_username",
"TELEGRAM_WEBHOOK_SECRET": "$SSM_PREFIX/$SSM_ENV/telegram_webhook_secret",
"SOCIAL_ENCRYPTION_KEY": "$SSM_PREFIX/$SSM_ENV/social_encryption_key",
"TWITTER_CLIENT_ID": "$SSM_PREFIX/$SSM_ENV/twitter_client_id",
"TWITTER_CLIENT_SECRET": "$SSM_PREFIX/$SSM_ENV/twitter_client_secret",
Expand Down
6 changes: 6 additions & 0 deletions backend/deploy-apprunner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ if aws apprunner describe-service --service-arn arn:aws:apprunner:$REGION:$ACCOU
"DEFILLAMA_FEES_RANK": "$SSM_PREFIX/prod/defillama_fees_rank",
"DEFILLAMA_FEES_RANK_URL": "$SSM_PREFIX/prod/defillama_fees_rank_url",
"TELEGRAM_MEMBERS": "$SSM_PREFIX/prod/telegram_members",
"TELEGRAM_BOT_TOKEN": "$SSM_PREFIX/prod/telegram_bot_token",
"TELEGRAM_BOT_USERNAME": "$SSM_PREFIX/prod/telegram_bot_username",
"TELEGRAM_WEBHOOK_SECRET": "$SSM_PREFIX/prod/telegram_webhook_secret",
"SOCIAL_ENCRYPTION_KEY": "$SSM_PREFIX/prod/social_encryption_key",
"TWITTER_CLIENT_ID": "$SSM_PREFIX/prod/twitter_client_id",
"TWITTER_CLIENT_SECRET": "$SSM_PREFIX/prod/twitter_client_secret",
Expand Down Expand Up @@ -351,6 +354,9 @@ else
"DEFILLAMA_FEES_RANK": "$SSM_PREFIX/prod/defillama_fees_rank",
"DEFILLAMA_FEES_RANK_URL": "$SSM_PREFIX/prod/defillama_fees_rank_url",
"TELEGRAM_MEMBERS": "$SSM_PREFIX/prod/telegram_members",
"TELEGRAM_BOT_TOKEN": "$SSM_PREFIX/prod/telegram_bot_token",
"TELEGRAM_BOT_USERNAME": "$SSM_PREFIX/prod/telegram_bot_username",
"TELEGRAM_WEBHOOK_SECRET": "$SSM_PREFIX/prod/telegram_webhook_secret",
"SOCIAL_ENCRYPTION_KEY": "$SSM_PREFIX/prod/social_encryption_key",
"TWITTER_CLIENT_ID": "$SSM_PREFIX/prod/twitter_client_id",
"TWITTER_CLIENT_SECRET": "$SSM_PREFIX/prod/twitter_client_secret",
Expand Down
2 changes: 0 additions & 2 deletions backend/ethereum_auth/email_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,6 @@ def _create_user_from_pending_signup(self, pending_signup, email):
name=profile.get('name', ''),
description=profile.get('description', ''),
website=profile.get('website', ''),
telegram_handle=profile.get('telegram_handle', ''),
linkedin_handle=profile.get('linkedin_handle', ''),
is_email_verified=True,
email_verified_at=timezone.now(),
Expand All @@ -515,7 +514,6 @@ def _clean_profile_data(data):
'name',
'description',
'website',
'telegram_handle',
'linkedin_handle',
'selected_role',
}
Expand Down
3 changes: 2 additions & 1 deletion backend/ethereum_auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@
LOGIN_STATEMENT = 'Sign in with Ethereum to GenLayer Testnet Contributions'
email_verification_service = EmailVerificationService()
turnstile_verifier = TurnstileVerifier()
# telegram_handle removed: Telegram links only through the bot's verified
# private TelegramConnection, never as a user-typed handle.
PENDING_SIGNUP_PROFILE_FIELDS = {
'name',
'description',
'website',
'telegram_handle',
'linkedin_handle',
'selected_role',
}
Expand Down
Loading
Loading