diff --git a/options/locale/locale_cs-CZ.json b/options/locale/locale_cs-CZ.json index 52e9940db1..8b2045b5ae 100644 --- a/options/locale/locale_cs-CZ.json +++ b/options/locale/locale_cs-CZ.json @@ -3057,6 +3057,17 @@ "gpg.error.no_gpg_keys_found": "V databázi nebyl nalezen žádný známý klíč pro tento podpis", "gpg.error.not_signed_commit": "Nepodepsaná revize", "gpg.error.failed_retrieval_gpg_keys": "Nelze získat žádný klíč propojený s účtem přispěvatele", + "gpg.signin.title": "Přihlásit se", + "gpg.signin.nonce_label": "Podepište tento nonce svým soukromým klíčem", + "gpg.signin.command_hint": "Spusťte tento příkaz a vložte výstup níže:", + "gpg.signin.copy": "Kopírovat", + "gpg.signin.signed_output": "Podepsaný výstup", + "gpg.signin.submit": "Přihlásit se", + "gpg.signup.title": "Vytvořit účet", + "gpg.signup.paste_key": "Vložte svůj veřejný GPG klíč", + "gpg.signup.proceed": "Pokračovat", + "gpg.signup.submit": "Ověřit a vytvořit účet", + "gpg.signup.already_have_account": "Již máte účet?", "units.unit": "Jednotka", "units.error.no_unit_allowed_repo": "Nejste oprávněni přistupovat k žádné části tohoto repozitáře.", "units.error.unit_not_allowed": "Nejste oprávněni přistupovat k této části repozitáře.", @@ -3307,4 +3318,4 @@ "git.filemode.executable_file": "Spustitelný soubor", "git.filemode.symbolic_link": "Symbolický odkaz", "git.filemode.submodule": "Submodul" -} +} \ No newline at end of file diff --git a/options/locale/locale_de-DE.json b/options/locale/locale_de-DE.json index 53d7d4d97d..3642e38391 100644 --- a/options/locale/locale_de-DE.json +++ b/options/locale/locale_de-DE.json @@ -3005,6 +3005,17 @@ "gpg.error.no_gpg_keys_found": "Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden", "gpg.error.not_signed_commit": "Kein signierter Commit", "gpg.error.failed_retrieval_gpg_keys": "Fehler beim Abrufen eines Keys des Commiter-Kontos", + "gpg.signin.title": "Anmelden", + "gpg.signin.nonce_label": "Signiere diesen Nonce mit deinem privaten Schlüssel", + "gpg.signin.command_hint": "Führe diesen Befehl aus und füge die Ausgabe unten ein:", + "gpg.signin.copy": "Kopieren", + "gpg.signin.signed_output": "Signierte Ausgabe", + "gpg.signin.submit": "Anmelden", + "gpg.signup.title": "Konto erstellen", + "gpg.signup.paste_key": "Füge deinen öffentlichen GPG-Schlüssel ein", + "gpg.signup.proceed": "Weiter", + "gpg.signup.submit": "Verifizieren & Konto erstellen", + "gpg.signup.already_have_account": "Bereits ein Konto?", "units.unit": "Einheit", "units.error.no_unit_allowed_repo": "Du hast keine Berechtigung, auf etwas in diesem Repository zuzugreifen.", "units.error.unit_not_allowed": "Du hast keine Berechtigung, um auf diesen Repository-Bereich zuzugreifen.", @@ -3249,4 +3260,4 @@ "git.filemode.executable_file": "Ausführbare Datei", "git.filemode.symbolic_link": "Softlink", "git.filemode.submodule": "Submodul" -} +} \ No newline at end of file diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 90c7c71fb7..5245072519 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3503,6 +3503,17 @@ "gpg.error.failed_retrieval_gpg_keys": "Failed to retrieve any key attached to the committer's account", "gpg.error.probable_bad_signature": "WARNING! Although there is a key with this ID in the database, it does not verify this commit! This commit is SUSPICIOUS.", "gpg.error.probable_bad_default_signature": "WARNING! Although the default key has this ID, it does not verify this commit! This commit is SUSPICIOUS.", + "gpg.signin.title": "Sign In", + "gpg.signin.nonce_label": "Sign this nonce with your private key", + "gpg.signin.command_hint": "Run this command, then paste the output below:", + "gpg.signin.copy": "Copy", + "gpg.signin.signed_output": "Signed output", + "gpg.signin.submit": "Sign In", + "gpg.signup.title": "Create your account", + "gpg.signup.paste_key": "Paste your GPG public key", + "gpg.signup.proceed": "Proceed", + "gpg.signup.submit": "Verify & Create Account", + "gpg.signup.already_have_account": "Already have an account?", "units.unit": "Unit", "units.error.no_unit_allowed_repo": "You are not allowed to access any section of this repository.", "units.error.unit_not_allowed": "You are not allowed to access this repository section.", @@ -3888,4 +3899,4 @@ "actions.general.cross_repo_selected": "Selected repositories", "actions.general.cross_repo_target_repos": "Target Repositories", "actions.general.cross_repo_add": "Add Target Repository" -} +} \ No newline at end of file diff --git a/options/locale/locale_es-ES.json b/options/locale/locale_es-ES.json index 04727abb5b..c2e1fa7a4c 100644 --- a/options/locale/locale_es-ES.json +++ b/options/locale/locale_es-ES.json @@ -2732,6 +2732,17 @@ "gpg.error.no_gpg_keys_found": "No se encontró ninguna clave conocida en la base de datos para esta firma", "gpg.error.not_signed_commit": "No es un commit firmado", "gpg.error.failed_retrieval_gpg_keys": "No se pudo recuperar cualquier clave adjunta a la cuenta del committer", + "gpg.signin.title": "Iniciar sesión", + "gpg.signin.nonce_label": "Firma este nonce con tu clave privada", + "gpg.signin.command_hint": "Ejecuta este comando y pega el resultado abajo:", + "gpg.signin.copy": "Copiar", + "gpg.signin.signed_output": "Salida firmada", + "gpg.signin.submit": "Iniciar sesión", + "gpg.signup.title": "Crear tu cuenta", + "gpg.signup.paste_key": "Pega tu clave pública GPG", + "gpg.signup.proceed": "Continuar", + "gpg.signup.submit": "Verificar y crear cuenta", + "gpg.signup.already_have_account": "¿Ya tienes una cuenta?", "units.unit": "Unidad", "units.error.no_unit_allowed_repo": "No tiene permisos para acceder a ninguna sección de este repositorio.", "units.error.unit_not_allowed": "No tiene permisos para acceder a esta sección del repositorio.", @@ -2960,4 +2971,4 @@ "git.filemode.executable_file": "Archivo ejecutable", "git.filemode.symbolic_link": "Enlace simbólico", "git.filemode.submodule": "Submódulo" -} +} \ No newline at end of file diff --git a/options/locale/locale_fr-FR.json b/options/locale/locale_fr-FR.json index 5cfa7498b9..b07f81deae 100644 --- a/options/locale/locale_fr-FR.json +++ b/options/locale/locale_fr-FR.json @@ -3498,6 +3498,17 @@ "gpg.error.failed_retrieval_gpg_keys": "Impossible de récupérer la clé liée au compte de l'auteur", "gpg.error.probable_bad_signature": "AVERTISSEMENT ! Bien qu’il y ait une clé avec cet ID dans la base de données, elle ne vérifie pas cette révision ! Cette révision est SUSPECTE.", "gpg.error.probable_bad_default_signature": "AVERTISSEMENT ! Bien que la clé par défaut ait cet ID, elle ne vérifie pas cette révision ! Cette révision est SUSPECTE.", + "gpg.signin.title": "Se connecter", + "gpg.signin.nonce_label": "Signez ce nonce avec votre clé privée", + "gpg.signin.command_hint": "Exécutez cette commande puis collez le résultat ci-dessous :", + "gpg.signin.copy": "Copier", + "gpg.signin.signed_output": "Sortie signée", + "gpg.signin.submit": "Se connecter", + "gpg.signup.title": "Créer votre compte", + "gpg.signup.paste_key": "Collez votre clé publique GPG", + "gpg.signup.proceed": "Continuer", + "gpg.signup.submit": "Vérifier & créer le compte", + "gpg.signup.already_have_account": "Vous avez déjà un compte ?", "units.unit": "Ressource", "units.error.no_unit_allowed_repo": "Vous n'êtes pas autorisé à accéder à n'importe quelle section de ce dépôt.", "units.error.unit_not_allowed": "Vous n'êtes pas autorisé à accéder à cette section du dépôt.", @@ -3866,4 +3877,4 @@ "actions.general.cross_repo_selected": "Dépôts sélectionnés", "actions.general.cross_repo_target_repos": "Dépôts cibles", "actions.general.cross_repo_add": "Ajouter un dépôt cible" -} +} \ No newline at end of file diff --git a/options/locale/locale_ko-KR.json b/options/locale/locale_ko-KR.json index 121d0fb8bf..7c96863d93 100644 --- a/options/locale/locale_ko-KR.json +++ b/options/locale/locale_ko-KR.json @@ -3499,6 +3499,17 @@ "gpg.error.failed_retrieval_gpg_keys": "커미터 계정에 연결된 키를 가져오지 못함", "gpg.error.probable_bad_signature": "경고! 이 ID를 가진 키가 데이터베이스에 있지만 이 커밋을 검증하지 않습니다! 이 커밋은 의심스럽습니다.", "gpg.error.probable_bad_default_signature": "경고! 기본 키는 이 ID를 가지지만 이 커밋을 검증하지 않습니다! 이 커밋은 의심스럽습니다.", + "gpg.signin.title": "로그인", + "gpg.signin.nonce_label": "개인 키로 이 nonce에 서명하세요", + "gpg.signin.command_hint": "이 명령을 실행하고 아래에 출력을 붙여넣으세요:", + "gpg.signin.copy": "복사", + "gpg.signin.signed_output": "서명된 출력", + "gpg.signin.submit": "로그인", + "gpg.signup.title": "계정 만들기", + "gpg.signup.paste_key": "GPG 공개 키를 붙여넣으세요", + "gpg.signup.proceed": "계속", + "gpg.signup.submit": "확인 및 계정 생성", + "gpg.signup.already_have_account": "이미 계정이 있으신가요?", "units.unit": "단위", "units.error.no_unit_allowed_repo": "이 리포지토리의 어떤 섹션에도 접근할 수 없습니다.", "units.error.unit_not_allowed": "이 리포지토리 섹션에 접근할 수 없습니다.", @@ -3879,4 +3890,4 @@ "actions.general.cross_repo_selected": "선택된 리포지토리", "actions.general.cross_repo_target_repos": "대상 리포지토리", "actions.general.cross_repo_add": "대상 리포지토리 추가" -} +} \ No newline at end of file diff --git a/options/locale/locale_ru-RU.json b/options/locale/locale_ru-RU.json index 9dc16ab820..1eba4af8b7 100644 --- a/options/locale/locale_ru-RU.json +++ b/options/locale/locale_ru-RU.json @@ -2755,6 +2755,17 @@ "gpg.error.no_gpg_keys_found": "Не найден ключ, соответствующий данной подписи", "gpg.error.not_signed_commit": "Неподписанный коммит", "gpg.error.failed_retrieval_gpg_keys": "Не удалось получить ни одного ключа GPG автора коммита", + "gpg.signin.title": "Войти", + "gpg.signin.nonce_label": "Подпишите этот nonce своим приватным ключом", + "gpg.signin.command_hint": "Выполните команду и вставьте результат ниже:", + "gpg.signin.copy": "Копировать", + "gpg.signin.signed_output": "Подписанный вывод", + "gpg.signin.submit": "Войти", + "gpg.signup.title": "Создать аккаунт", + "gpg.signup.paste_key": "Вставьте ваш публичный GPG-ключ", + "gpg.signup.proceed": "Продолжить", + "gpg.signup.submit": "Подтвердить и создать аккаунт", + "gpg.signup.already_have_account": "Уже есть аккаунт?", "units.unit": "Элемент", "units.error.no_unit_allowed_repo": "У вас нет доступа ни к одному разделу этого репозитория.", "units.error.unit_not_allowed": "У вас нет доступа к этому разделу репозитория.", @@ -2983,4 +2994,4 @@ "git.filemode.executable_file": "Исполняемый файл", "git.filemode.symbolic_link": "Символическая ссылка", "git.filemode.submodule": "Подмодуль" -} +} \ No newline at end of file diff --git a/options/locale/locale_zh-CN.json b/options/locale/locale_zh-CN.json index ba52e2e79d..b1761f3d40 100644 --- a/options/locale/locale_zh-CN.json +++ b/options/locale/locale_zh-CN.json @@ -3498,6 +3498,17 @@ "gpg.error.failed_retrieval_gpg_keys": "找不到任何与该提交者账号相关的密钥", "gpg.error.probable_bad_signature": "警告!虽然数据库中有一个此 ID 的密钥,但它没有验证此提交!此提交是可疑的。", "gpg.error.probable_bad_default_signature": "警告!虽然默认密钥拥有此 ID,但它没有验证此提交!此提交是可疑的。", + "gpg.signin.title": "登录", + "gpg.signin.nonce_label": "使用您的私钥签署此随机数", + "gpg.signin.command_hint": "运行此命令,然后将输出粘贴到下方:", + "gpg.signin.copy": "复制", + "gpg.signin.signed_output": "签名输出", + "gpg.signin.submit": "登录", + "gpg.signup.title": "创建账户", + "gpg.signup.paste_key": "粘贴您的 GPG 公钥", + "gpg.signup.proceed": "继续", + "gpg.signup.submit": "验证并创建账户", + "gpg.signup.already_have_account": "已有账户?", "units.unit": "单元", "units.error.no_unit_allowed_repo": "您没有被允许访问此仓库的任何单元。", "units.error.unit_not_allowed": "您没有权限访问此仓库单元", @@ -3866,4 +3877,4 @@ "actions.general.cross_repo_selected": "选择的仓库", "actions.general.cross_repo_target_repos": "目标仓库", "actions.general.cross_repo_add": "添加目标仓库" -} +} \ No newline at end of file diff --git a/templates/shared/user/profile_big_avatar.tmpl b/templates/shared/user/profile_big_avatar.tmpl index b70013c7f0..f12edc263a 100644 --- a/templates/shared/user/profile_big_avatar.tmpl +++ b/templates/shared/user/profile_big_avatar.tmpl @@ -68,6 +68,7 @@ {{end}} {{if $.RenderedDescription}}
  • + {{svg "octicon-info"}}
    {{$.RenderedDescription}}
  • {{end}} @@ -79,6 +80,16 @@ {{end}} {{end}} + {{if .ContextUser.GPGKeys}} + {{if .GPGKeys}} +
  • + {{svg "octicon-key"}} + + GPG: {{(index .GPGKeys 0).PaddedKeyID}}{{if gt (len .GPGKeys) 1}} +{{.GPGKeysExtra}}{{end}} + +
  • + {{end}} + {{end}}
  • {{svg "octicon-calendar"}} {{ctx.Locale.Tr "user.joined_on" (DateUtils.AbsoluteShort .ContextUser.CreatedUnix)}}
  • {{if and .Orgs .HasOrgsVisible}}
  • diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl index 864e0993d6..1fc62961ec 100644 --- a/templates/user/auth/signin_inner.tmpl +++ b/templates/user/auth/signin_inner.tmpl @@ -1,76 +1,45 @@
    - {{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}} - {{template "base/alert" .}} - {{end}}

    - {{if .LinkAccountMode}} - {{ctx.Locale.Tr "auth.oauth_signin_title"}} - {{else}} - {{ctx.Locale.Tr "auth.login_userpass"}} - {{end}} + {{ctx.Locale.Tr "gpg.signin.nonce_label"}}

    - {{if .EnablePasswordSignInForm}} -
    -
    - - -
    - {{if or (not .DisablePassword) .LinkAccountMode}} -
    -
    - - {{ctx.Locale.Tr "auth.forgot_password"}} -
    - -
    - {{end}} - {{if not .LinkAccountMode}} -
    -
    - - -
    -
    - {{end}} - - {{template "user/auth/captcha" .}} + + {{.CsrfTokenHtml}} + {{template "base/alert" .}} +
    - + + +
    +
    +

    {{ctx.Locale.Tr "gpg.signin.command_hint"}}

    +
    + + +
    +
    +
    + + +
    +
    +
    - {{end}}{{/*end if .EnablePasswordSignInForm*/}} - {{$showExternalAuthMethods := or .OAuth2Providers .EnableOpenIDSignIn .EnableSSPI}} - {{if and $showExternalAuthMethods .EnablePasswordSignInForm}} -
    {{ctx.Locale.Tr "sign_in_or"}}
    - {{end}} - {{if $showExternalAuthMethods}} - {{template "user/auth/external_auth_methods" .}} - {{end}}
    -{{if or .EnablePasskeyAuth .ShowRegistrationButton}} +{{if .ShowRegistrationButton}}
    -
    - {{if .EnablePasskeyAuth}} - {{template "user/auth/webauthn_error" .}} - - {{end}} - - {{if .ShowRegistrationButton}} -
    - {{ctx.Locale.Tr "auth.need_account"}} - {{ctx.Locale.Tr "auth.sign_up_now"}} -
    - {{end}} +
    +
    + {{ctx.Locale.Tr "auth.need_account"}} + {{ctx.Locale.Tr "auth.sign_up_now"}} +
    -{{end}} +{{end}} \ No newline at end of file diff --git a/templates/user/auth/signup_inner.tmpl b/templates/user/auth/signup_inner.tmpl index 0c1f1a3906..a7815f4d35 100644 --- a/templates/user/auth/signup_inner.tmpl +++ b/templates/user/auth/signup_inner.tmpl @@ -1,70 +1,54 @@ -
    +

    - {{if .LinkAccountMode}} - {{ctx.Locale.Tr "auth.oauth_signup_title"}} - {{else}} - {{ctx.Locale.Tr "sign_up"}} - {{end}} + {{ctx.Locale.Tr "gpg.signup.title"}}

    - {{if .IsFirstTimeRegistration}} -

    {{ctx.Locale.Tr "auth.sign_up_tip"}}

    - {{end}} -
    - {{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}} - {{template "base/alert" .}} - {{end}} - {{if .DisableRegistration}} -

    {{ctx.Locale.Tr "auth.disable_register_prompt"}}

    - {{else}} -
    - - + + {{.CsrfTokenHtml}} + + + +
    + {{template "base/alert" .}} +
    + +
    -
    - - -
    - - {{if not .DisablePassword}} -
    - - -
    -
    - - -
    - {{end}} - - {{template "user/auth/captcha" .}} -
    - +
    - {{end}} - {{$showExternalAuthMethods := or .OAuth2Providers .EnableOpenIDSignIn .EnableSSPI}} - {{if $showExternalAuthMethods}} -
    {{ctx.Locale.Tr "sign_in_or"}}
    - {{template "user/auth/external_auth_methods" .}} - {{end}} +
    + +
    - {{if not .LinkAccountMode}}
    - {{ctx.Locale.Tr "auth.already_have_account"}} - {{ctx.Locale.Tr "auth.sign_in_now"}} + {{ctx.Locale.Tr "gpg.signup.already_have_account"}} + {{ctx.Locale.Tr "gpg.signin.title"}}
    - {{end}} -
    +
    \ No newline at end of file diff --git a/web_src/js/features/user-auth-gpg-signin.ts b/web_src/js/features/user-auth-gpg-signin.ts new file mode 100644 index 0000000000..803adca766 --- /dev/null +++ b/web_src/js/features/user-auth-gpg-signin.ts @@ -0,0 +1,31 @@ +export function initGpgSignin() { + const tokenField = document.getElementById('token-field') as HTMLInputElement; + if (!tokenField) return; + + const nonce = (() => { + const arr = new Uint8Array(32); + crypto.getRandomValues(arr); + return Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join(''); + })(); + + tokenField.value = nonce; + (document.getElementById('hidden-nonce') as HTMLInputElement).value = nonce; + (document.getElementById('sign-command') as HTMLInputElement).value = + `echo "${nonce}" | gpg -a --detach-sig`; + + document.getElementById('btn-copy-cmd')?.addEventListener('click', () => { + navigator.clipboard.writeText( + (document.getElementById('sign-command') as HTMLInputElement).value + ); + }); + + document.getElementById('signin-form')?.addEventListener('submit', (e) => { + const sig = (document.getElementById('gpg_signature') as HTMLTextAreaElement).value.trim(); + if (!sig || + (!sig.startsWith('-----BEGIN PGP SIGNED MESSAGE-----') && + !sig.startsWith('-----BEGIN PGP SIGNATURE-----'))) { + e.preventDefault(); + alert('Paste the GPG signed output.'); + } + }); +} \ No newline at end of file diff --git a/web_src/js/features/user-auth-gpg-signup.ts b/web_src/js/features/user-auth-gpg-signup.ts new file mode 100644 index 0000000000..3990b24bd6 --- /dev/null +++ b/web_src/js/features/user-auth-gpg-signup.ts @@ -0,0 +1,40 @@ +export function initGpgSignup() { + const btnProceed = document.getElementById('btn-proceed'); + if (!btnProceed) return; + + const nonce = (() => { + const arr = new Uint8Array(32); + crypto.getRandomValues(arr); + return Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join(''); + })(); + + btnProceed.addEventListener('click', () => { + const key = (document.getElementById('gpg_key') as HTMLTextAreaElement).value.trim(); + if (!key || !key.startsWith('-----BEGIN PGP PUBLIC KEY BLOCK-----')) { + alert('Paste a valid armored GPG public key.'); + return; + } + (document.getElementById('token-field') as HTMLInputElement).value = nonce; + (document.getElementById('hidden-nonce') as HTMLInputElement).value = nonce; + (document.getElementById('hidden-gpg-key') as HTMLInputElement).value = key; + (document.getElementById('sign-command') as HTMLInputElement).value = + `echo "${nonce}" | gpg -a --detach-sig`; + document.getElementById('step-key')!.style.display = 'none'; + document.getElementById('step-sign')!.style.display = 'block'; + }); + + document.getElementById('signup-form')?.addEventListener('submit', (e) => { + const sig = (document.getElementById('gpg_signature') as HTMLTextAreaElement).value.trim(); + if (!sig || + (!sig.startsWith('-----BEGIN PGP SIGNED MESSAGE-----') && + !sig.startsWith('-----BEGIN PGP SIGNATURE-----'))) { + e.preventDefault(); + alert('Paste the GPG signed output.'); + } + }); + + document.getElementById('btn-copy-cmd')?.addEventListener('click', () => { + const cmd = (document.getElementById('sign-command') as HTMLInputElement).value; + navigator.clipboard.writeText(cmd); + }); +} \ No newline at end of file diff --git a/web_src/js/index.ts b/web_src/js/index.ts index 6f081f3020..863b65c871 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -66,6 +66,8 @@ import {initActionsPermissionsForm} from './features/common-actions-permissions. import {initRefIssueContextPopup} from './features/ref-issue.ts'; import {initGlobalShortcut} from './modules/shortcut.ts'; import {initDevtest} from './modules/devtest.ts'; +import {initGpgSignup} from './features/user-auth-gpg-signup.js'; +import {initGpgSignin} from './features/user-auth-gpg-signin.js'; const initStartTime = performance.now(); const initPerformanceTracer = callInitFunctions([ @@ -84,6 +86,8 @@ const initPerformanceTracer = callInitFunctions([ initGlobalDeleteButton, initGlobalInput, initGlobalShortcut, + initGpgSignup, + initGpgSignin, initCommonOrganization, initCommonIssueListQuickGoto,