diff --git a/.github/workflows/deploy-functions.yml b/.github/workflows/deploy-functions.yml new file mode 100644 index 0000000..3457feb --- /dev/null +++ b/.github/workflows/deploy-functions.yml @@ -0,0 +1,32 @@ +name: Deploy Edge Functions + +# Deployt die Supabase Edge Functions automatisch, sobald sich etwas unter +# supabase/functions/** (oder deren Config) auf main aendert. Zusaetzlich +# manuell ausloesbar (Actions -> Deploy Edge Functions -> Run workflow). +on: + push: + branches: [main] + paths: + - "supabase/functions/**" + - "supabase/config.toml" + - ".github/workflows/deploy-functions.yml" + workflow_dispatch: {} + +jobs: + deploy: + runs-on: ubuntu-latest + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + PROJECT_REF: ljmaeuarmfazvwoohlxk + steps: + - uses: actions/checkout@v4 + + - uses: supabase/setup-cli@v1 + with: + version: latest + + - name: Deploy Edge Functions + run: | + supabase functions deploy send-otp --project-ref "$PROJECT_REF" + supabase functions deploy verify-otp --project-ref "$PROJECT_REF" + supabase functions deploy send-push --project-ref "$PROJECT_REF" diff --git a/src/components/auth/login-form.tsx b/src/components/auth/login-form.tsx index 10b97b5..70fb867 100644 --- a/src/components/auth/login-form.tsx +++ b/src/components/auth/login-form.tsx @@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { enableDemoMode } from "@/lib/demo"; +import { normalizePhoneDE, isValidPhoneDE } from "@/lib/phone"; interface LoginFormProps { onSendOtp: (phone: string) => Promise<{ request_id: string }>; @@ -26,7 +27,12 @@ export function LoginForm({ onSendOtp, onVerifyOtp }: LoginFormProps) { setLoading(true); try { - const cleanPhone = phone.replace(/\s/g, ""); + const cleanPhone = normalizePhoneDE(phone); + if (!isValidPhoneDE(cleanPhone)) { + setError("Bitte gib eine gueltige Handynummer ein, z.B. +49 151 12345678"); + setLoading(false); + return; + } const result = await onSendOtp(cleanPhone); setRequestId(result.request_id); setStep("otp"); @@ -49,7 +55,7 @@ export function LoginForm({ onSendOtp, onVerifyOtp }: LoginFormProps) { setLoading(true); try { - const cleanPhone = phone.replace(/\s/g, ""); + const cleanPhone = normalizePhoneDE(phone); await onVerifyOtp(cleanPhone, code, requestId); } catch (err) { setError((err as Error).message || "Hm, der Code passt nicht. Nochmal?"); @@ -80,10 +86,15 @@ export function LoginForm({ onSendOtp, onVerifyOtp }: LoginFormProps) { type="tel" value={phone} onChange={(e) => setPhone(e.target.value)} - placeholder="+49 170 1234567" + placeholder="+49 151 12345678" autoComplete="tel" required /> +
+ Format: +49 151 12345678 — + also mit Laendervorwahl, aber ohne die 0{" "} + nach der +49 (nicht +49 0151…). +

Wir schicken dir den Code via Telegram.

diff --git a/src/lib/phone.ts b/src/lib/phone.ts new file mode 100644 index 0000000..ebfda55 --- /dev/null +++ b/src/lib/phone.ts @@ -0,0 +1,24 @@ +/** + * Normalisiert eine (deutsche) Telefonnummer auf E.164-Format (+49...). + * + * Wichtig: entfernt die nationale Trunk-"0" direkt nach der Laendervorwahl, + * damit "+49 0151 234" und "+49 151 234" dieselbe Nummer ergeben und keine + * doppelten Accounts entstehen. + */ +export function normalizePhoneDE(raw: string): string { + let s = raw.replace(/[^\d+]/g, ""); // nur Ziffern und + + if (!s) return ""; + + if (s.startsWith("00")) s = "+" + s.slice(2); // 0049... -> +49... + if (!s.startsWith("+") && s.startsWith("49")) s = "+" + s; // 49... -> +49... + if (s.startsWith("0")) s = "+49" + s.slice(1); // 0151... -> +49151... + if (!s.startsWith("+")) s = "+49" + s; // 151... -> +49151... + + s = s.replace(/^\+490+/, "+49"); // +490151... -> +49151... + return s; +} + +/** Grobe Plausibilitaetspruefung: +49 gefolgt von 6-13 Ziffern. */ +export function isValidPhoneDE(normalized: string): boolean { + return /^\+49\d{6,13}$/.test(normalized); +} diff --git a/supabase/functions/send-otp/index.ts b/supabase/functions/send-otp/index.ts index cd0ab03..08738e8 100644 --- a/supabase/functions/send-otp/index.ts +++ b/supabase/functions/send-otp/index.ts @@ -7,13 +7,26 @@ const corsHeaders = { "authorization, x-client-info, apikey, content-type", }; +// Normalisiert auf E.164 (+49...) und entfernt die nationale Trunk-0, +// damit "+490151..." und "+49151..." nicht als zwei Accounts enden. +function normalizePhoneDE(raw: string): string { + let s = (raw ?? "").replace(/[^\d+]/g, ""); + if (!s) return ""; + if (s.startsWith("00")) s = "+" + s.slice(2); + if (!s.startsWith("+") && s.startsWith("49")) s = "+" + s; + if (s.startsWith("0")) s = "+49" + s.slice(1); + if (!s.startsWith("+")) s = "+49" + s; + return s.replace(/^\+490+/, "+49"); +} + Deno.serve(async (req) => { if (req.method === "OPTIONS") { return new Response("ok", { headers: corsHeaders }); } try { - const { phone } = await req.json(); + const body = await req.json(); + const phone = normalizePhoneDE(body.phone); if (!phone || !/^\+\d{8,15}$/.test(phone)) { return new Response( diff --git a/supabase/functions/verify-otp/index.ts b/supabase/functions/verify-otp/index.ts index e5bedb2..759b8cc 100644 --- a/supabase/functions/verify-otp/index.ts +++ b/supabase/functions/verify-otp/index.ts @@ -22,6 +22,18 @@ function emailForPhone(phone: string): string { return `${phone.replace(/\D/g, "")}@${EMAIL_DOMAIN}`; } +// Normalisiert auf E.164 (+49...) und entfernt die nationale Trunk-0, damit +// dieselbe Nummer immer denselben User/E-Mail-Alias ergibt (keine Duplikate). +function normalizePhoneDE(raw: string): string { + let s = (raw ?? "").replace(/[^\d+]/g, ""); + if (!s) return ""; + if (s.startsWith("00")) s = "+" + s.slice(2); + if (!s.startsWith("+") && s.startsWith("49")) s = "+" + s; + if (s.startsWith("0")) s = "+49" + s.slice(1); + if (!s.startsWith("+")) s = "+49" + s; + return s.replace(/^\+490+/, "+49"); +} + // deno-lint-ignore no-explicit-any async function findUserByEmail(supabase: any, email: string) { let page = 1; @@ -47,7 +59,7 @@ Deno.serve(async (req) => { try { const body = await req.json(); - const phone: string | undefined = body.phone; + const phone = normalizePhoneDE(body.phone ?? ""); const code: string | undefined = body.code; let requestId: string | undefined = body.request_id;