- API (api/src/auth.ts): scrypt password hashes; random session tokens stored only as SHA-256; 30-day sliding sessions; rate limits on sign-in (per IP and per email) and sign-up; changing the password signs out other devices; deleting the account removes its data. - Site: /signup, /signin (returns to the page the reader came from), /account; header link; plain forms, no JavaScript needed. Session in an HTTP-only, SameSite=Lax cookie (Secure over HTTPS). - CSRF: Astro's origin check, with the site's hostnames listed (SITE_HOSTS) so its own form posts pass and other sites' are refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
2.8 KiB
Plaintext
64 lines
2.8 KiB
Plaintext
---
|
|
import Base from '../layouts/Base.astro';
|
|
import { auth, COOKIE } from '../lib/auth';
|
|
import { ud } from '../lib/urdu';
|
|
|
|
const user = Astro.locals.user;
|
|
if (!user) return Astro.redirect('/signin?next=/account');
|
|
const token = Astro.cookies.get(COOKIE)!.value;
|
|
let error = '', done = '';
|
|
if (Astro.request.method === 'POST') {
|
|
const form = await Astro.request.formData();
|
|
const act = form.get('act');
|
|
if (act === 'signout') {
|
|
await auth('signout', { token });
|
|
Astro.cookies.delete(COOKIE, { path: '/' });
|
|
return Astro.redirect('/');
|
|
}
|
|
if (act === 'password') {
|
|
if (form.get('next') !== form.get('next2')) error = 'نئے پاس ورڈ ایک جیسے نہیں';
|
|
else {
|
|
const r = await auth('password', { token, body: { current: form.get('current'), next: form.get('next') } });
|
|
r.ok ? (done = 'پاس ورڈ بدل گیا۔ دوسرے آلات سے لاگ آؤٹ کر دیا گیا۔') : (error = r.data.error);
|
|
}
|
|
}
|
|
if (act === 'delete') {
|
|
const r = await auth('delete', { token, body: { password: form.get('password') } });
|
|
if (r.ok) {
|
|
Astro.cookies.delete(COOKIE, { path: '/' });
|
|
return Astro.redirect('/');
|
|
}
|
|
error = r.data.error;
|
|
}
|
|
}
|
|
const since = new Date(user.created_at);
|
|
---
|
|
<Base title="میرا اکاؤنٹ">
|
|
<h1>میرا اکاؤنٹ</h1>
|
|
<p class="muted center"><bdi dir="ltr">{user.email}</bdi> · رکنیت: {ud(since.getFullYear())}</p>
|
|
{error && <p class="form-error" role="alert">{error}</p>}
|
|
{done && <p class="form-done" role="status">{done}</p>}
|
|
|
|
<form method="post" class="account-form">
|
|
<input type="hidden" name="act" value="signout" />
|
|
<button type="submit">لاگ آؤٹ</button>
|
|
</form>
|
|
|
|
<form method="post" class="account-form">
|
|
<h2>پاس ورڈ بدلیں</h2>
|
|
<input type="hidden" name="act" value="password" />
|
|
<label>موجودہ پاس ورڈ<input type="password" name="current" required autocomplete="current-password" dir="ltr" /></label>
|
|
<label>نیا پاس ورڈ<input type="password" name="next" required minlength="8" autocomplete="new-password" dir="ltr" /></label>
|
|
<label>نیا پاس ورڈ دوبارہ<input type="password" name="next2" required minlength="8" autocomplete="new-password" dir="ltr" /></label>
|
|
<button type="submit">پاس ورڈ بدلیں</button>
|
|
</form>
|
|
|
|
<form method="post" class="account-form danger">
|
|
<h2>اکاؤنٹ ختم کریں</h2>
|
|
<p class="muted">اکاؤنٹ اور اس کا تمام ڈیٹا مستقل طور پر حذف ہو جائے گا۔</p>
|
|
<input type="hidden" name="act" value="delete" />
|
|
<label>پاس ورڈ<input type="password" name="password" required autocomplete="current-password" dir="ltr" /></label>
|
|
<button type="submit">اکاؤنٹ مستقل طور پر ختم کریں</button>
|
|
</form>
|
|
</Base>
|