Gpg-based authorization #3

Open
d wants to merge 9 commits from gpg-auth into main
14 changed files with 252 additions and 125 deletions
+12 -1
View File
@@ -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"
}
}
+12 -1
View File
@@ -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"
}
}
+12 -1
View File
@@ -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"
}
}
+12 -1
View File
@@ -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"
}
}
+12 -1
View File
@@ -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 quil 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"
}
}
+12 -1
View File
@@ -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": "대상 리포지토리 추가"
}
}
+12 -1
View File
@@ -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": "Подмодуль"
}
}
+12 -1
View File
@@ -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": "添加目标仓库"
}
}
@@ -68,6 +68,7 @@
{{end}}
{{if $.RenderedDescription}}
<li>
{{svg "octicon-info"}}
<div class="render-content markup">{{$.RenderedDescription}}</div>
</li>
{{end}}
@@ -79,6 +80,16 @@
</li>
{{end}}
{{end}}
{{if .ContextUser.GPGKeys}}
{{if .GPGKeys}}
<li>
{{svg "octicon-key"}}
<a class="tw-font-mono tw-text-sm" href="{{AppSubUrl}}/{{$.ContextUser.Name}}.gpg" target="_blank">
GPG: {{(index .GPGKeys 0).PaddedKeyID}}{{if gt (len .GPGKeys) 1}} +{{.GPGKeysExtra}}{{end}}
</a>
</li>
{{end}}
{{end}}
<li>{{svg "octicon-calendar"}} <span>{{ctx.Locale.Tr "user.joined_on" (DateUtils.AbsoluteShort .ContextUser.CreatedUnix)}}</span></li>
{{if and .Orgs .HasOrgsVisible}}
<li>
+31 -62
View File
@@ -1,76 +1,45 @@
<div class="ui container fluid">
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
{{template "base/alert" .}}
{{end}}
<h4 class="ui top attached header center">
{{if .LinkAccountMode}}
{{ctx.Locale.Tr "auth.oauth_signin_title"}}
{{else}}
{{ctx.Locale.Tr "auth.login_userpass"}}
{{end}}
{{ctx.Locale.Tr "gpg.signin.nonce_label"}}
</h4>
<div class="ui attached segment">
{{if .EnablePasswordSignInForm}}
<form class="ui form" action="{{.SignInLink}}" method="post">
<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
<label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label>
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required tabindex="1">
</div>
{{if or (not .DisablePassword) .LinkAccountMode}}
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
<div class="tw-flex tw-mb-1">
<label for="password" class="tw-flex-1">{{ctx.Locale.Tr "password"}}</label>
<a href="{{AppSubUrl}}/user/forgot_password" tabindex="4">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
</div>
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="current-password" required tabindex="2">
</div>
{{end}}
{{if not .LinkAccountMode}}
<div class="inline field">
<div class="ui checkbox">
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
<input name="remember" type="checkbox" tabindex="5">
</div>
</div>
{{end}}
{{template "user/auth/captcha" .}}
<form class="ui form" action="{{.SignInLink}}" method="post" id="signin-form">
{{.CsrfTokenHtml}}
{{template "base/alert" .}}
<input type="hidden" id="hidden-nonce" name="nonce">
<div class="field">
<button class="ui primary button tw-w-full" tabindex="3">
{{if .LinkAccountMode}}
{{ctx.Locale.Tr "auth.oauth_signin_submit"}}
{{else}}
{{ctx.Locale.Tr "sign_in"}}
{{end}}
</button>
<label>{{ctx.Locale.Tr "gpg.signin.nonce_label"}}</label>
<input id="token-field" type="text" readonly style="font-family: monospace;">
</div>
<div class="ui info message">
<p>{{ctx.Locale.Tr "gpg.signin.command_hint"}}</p>
<div style="display: flex; gap: 6px;">
<input id="sign-command" type="text" readonly
style="font-family: monospace; min-width: 0; flex: 1;">
<button class="ui button" type="button" id="btn-copy-cmd">{{ctx.Locale.Tr "gpg.signin.copy"}}</button>
</div>
</div>
<div class="required field {{if .Err_GPGSign}}error{{end}}">
<label for="gpg_signature">{{ctx.Locale.Tr "gpg.signin.signed_output"}}</label>
<textarea id="gpg_signature" name="gpg_signature" rows="7"
placeholder="-----BEGIN PGP SIGNED MESSAGE-----" style="font-family: monospace;"
required>{{.gpg_signature}}</textarea>
</div>
<div class="inline field">
<button class="ui primary button tw-w-full" type="submit">{{ctx.Locale.Tr "gpg.signin.submit"}}</button>
</div>
</form>
{{end}}{{/*end if .EnablePasswordSignInForm*/}}
{{$showExternalAuthMethods := or .OAuth2Providers .EnableOpenIDSignIn .EnableSSPI}}
{{if and $showExternalAuthMethods .EnablePasswordSignInForm}}
<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div>
{{end}}
{{if $showExternalAuthMethods}}
{{template "user/auth/external_auth_methods" .}}
{{end}}
</div>
</div>
{{if or .EnablePasskeyAuth .ShowRegistrationButton}}
{{if .ShowRegistrationButton}}
<div class="ui container fluid">
<div class="ui attached segment header top tw-max-w-2xl tw-m-auto tw-flex tw-flex-col tw-items-center">
{{if .EnablePasskeyAuth}}
{{template "user/auth/webauthn_error" .}}
<a class="signin-passkey">{{ctx.Locale.Tr "auth.signin_passkey"}}</a>
{{end}}
{{if .ShowRegistrationButton}}
<div class="field">
<span>{{ctx.Locale.Tr "auth.need_account"}}</span>
<a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now"}}</a>
</div>
{{end}}
<div class="ui attached segment header top tw-flex tw-flex-col tw-items-center">
<div class="field">
<span>{{ctx.Locale.Tr "auth.need_account"}}</span>
<a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now"}}</a>
</div>
</div>
</div>
{{end}}
{{end}}
+39 -55
View File
@@ -1,70 +1,54 @@
<div class="ui container fluid{{if .LinkAccountMode}} icon{{end}}">
<div class="ui container fluid">
<h4 class="ui top attached header center">
{{if .LinkAccountMode}}
{{ctx.Locale.Tr "auth.oauth_signup_title"}}
{{else}}
{{ctx.Locale.Tr "sign_up"}}
{{end}}
{{ctx.Locale.Tr "gpg.signup.title"}}
</h4>
<div class="ui attached segment">
{{if .IsFirstTimeRegistration}}
<p>{{ctx.Locale.Tr "auth.sign_up_tip"}}</p>
{{end}}
<form class="ui form" action="{{.SignUpLink}}" method="post">
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}}
{{template "base/alert" .}}
{{end}}
{{if .DisableRegistration}}
<p>{{ctx.Locale.Tr "auth.disable_register_prompt"}}</p>
{{else}}
<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
<label for="user_name">{{ctx.Locale.Tr "username"}}</label>
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
<form class="ui form" action="{{.SignUpLink}}" method="post" id="signup-form">
{{.CsrfTokenHtml}}
<input type="hidden" id="hidden-gpg-key" name="gpg_key">
<input type="hidden" id="hidden-nonce" name="nonce">
<div id="step-key">
{{template "base/alert" .}}
<div class="required field {{if .Err_GPGKey}}error{{end}}">
<label>{{ctx.Locale.Tr "gpg.signup.paste_key"}}</label>
<textarea id="gpg_key" rows="7" placeholder="-----BEGIN PGP PUBLIC KEY BLOCK-----" style="font-family: monospace;" required>{{.gpg_key}}</textarea>
</div>
<div class="required field {{if .Err_Email}}error{{end}}">
<label for="email">{{ctx.Locale.Tr "email"}}</label>
<input id="email" name="email" type="email" value="{{.email}}" required>
</div>
{{if not .DisablePassword}}
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label>
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="new-password" required>
</div>
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
<label for="retype">{{ctx.Locale.Tr "re_type"}}</label>
<input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="new-password" required>
</div>
{{end}}
{{template "user/auth/captcha" .}}
<div class="inline field">
<button class="ui primary button tw-w-full">
{{if .LinkAccountMode}}
{{ctx.Locale.Tr "auth.oauth_signup_submit"}}
{{else}}
{{ctx.Locale.Tr "auth.create_new_account"}}
{{end}}
</button>
<button class="ui primary button tw-w-full" type="button" id="btn-proceed">{{ctx.Locale.Tr "gpg.signup.proceed"}}</button>
</div>
{{end}}
{{$showExternalAuthMethods := or .OAuth2Providers .EnableOpenIDSignIn .EnableSSPI}}
{{if $showExternalAuthMethods}}
<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div>
{{template "user/auth/external_auth_methods" .}}
{{end}}
</div>
<div id="step-sign" style="display: none;">
{{template "base/alert" .}}
<div class="field">
<label>{{ctx.Locale.Tr "gpg.signin.nonce"}}</label>
<input id="token-field" type="text" readonly style="font-family: monospace;">
</div>
<div class="ui info message">
<p>{{ctx.Locale.Tr "gpg.signin.command_hint"}}</p>
<div style="display: flex; gap: 6px;">
<input id="sign-command" type="text" readonly style="font-family: monospace; min-width: 0; flex: 1;">
<button class="ui button" type="button" id="btn-copy-cmd">{{ctx.Locale.Tr "gpg.signin.copy"}}</button>
</div>
</div>
<div class="required field {{if .Err_GPGSign}}error{{end}}">
<label for="gpg_signature">{{ctx.Locale.Tr "gpg.signin.signed_output"}}</label>
<textarea id="gpg_signature" name="gpg_signature" rows="7" placeholder="-----BEGIN PGP SIGNED MESSAGE-----" style="font-family: monospace;" required>{{.gpg_signature}}</textarea>
</div>
<div class="inline field">
<button class="ui primary button tw-w-full" type="submit">{{ctx.Locale.Tr "gpg.signup.submit"}}</button>
</div>
</div>
</form>
</div>
</div>
<div class="ui container fluid">
{{if not .LinkAccountMode}}
<div class="ui attached segment header top tw-flex tw-flex-col tw-items-center">
<div class="field">
<span>{{ctx.Locale.Tr "auth.already_have_account"}}</span>
<a href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "auth.sign_in_now"}}</a>
<span>{{ctx.Locale.Tr "gpg.signup.already_have_account"}}</span>
<a href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "gpg.signin.title"}}</a>
</div>
</div>
{{end}}
</div>
</div>
@@ -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.');
}
});
}
@@ -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);
});
}
+4
View File
@@ -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,