Gpg key more fixes #6
@@ -3321,5 +3321,6 @@
|
||||
"git.filemode.normal_file": "Normální soubor",
|
||||
"git.filemode.executable_file": "Spustitelný soubor",
|
||||
"git.filemode.symbolic_link": "Symbolický odkaz",
|
||||
"git.filemode.submodule": "Submodul"
|
||||
"git.filemode.submodule": "Submodul",
|
||||
"settings.oauth_applications_disabled": "OAuth aplikace jsou v <code>m8sh</code> zakázány, použijte osobní přístupový token."
|
||||
}
|
||||
@@ -3263,5 +3263,6 @@
|
||||
"git.filemode.normal_file": "Normale Datei",
|
||||
"git.filemode.executable_file": "Ausführbare Datei",
|
||||
"git.filemode.symbolic_link": "Softlink",
|
||||
"git.filemode.submodule": "Submodul"
|
||||
"git.filemode.submodule": "Submodul",
|
||||
"settings.oauth_applications_disabled": "OAuth-Anwendungen sind in <code>m8sh</code> deaktiviert, verwenden Sie einen persönlichen Zugriffstoken."
|
||||
}
|
||||
@@ -3015,5 +3015,6 @@
|
||||
"gpg.signup.paste_key": "Επικολλήστε το δημόσιο κλειδί GPG σας",
|
||||
"gpg.signup.proceed": "Συνέχεια",
|
||||
"gpg.signup.submit": "Επαλήθευση & Δημιουργία λογαριασμού",
|
||||
"gpg.signup.already_have_account": "Έχετε ήδη λογαριασμό;"
|
||||
"gpg.signup.already_have_account": "Έχετε ήδη λογαριασμό;",
|
||||
"settings.oauth_applications_disabled": "Οι εφαρμογές OAuth είναι απενεργοποιημένες στο <code>m8sh</code>, χρησιμοποιήστε προσωπικό διακριτικό πρόσβασης."
|
||||
}
|
||||
@@ -3902,5 +3902,6 @@
|
||||
"actions.general.cross_repo_desc": "Allow the selected repositories to be accessed (read-only) by all the repositories in this owner with GITEA_TOKEN when running Actions jobs.",
|
||||
"actions.general.cross_repo_selected": "Selected repositories",
|
||||
"actions.general.cross_repo_target_repos": "Target Repositories",
|
||||
"actions.general.cross_repo_add": "Add Target Repository"
|
||||
"actions.general.cross_repo_add": "Add Target Repository",
|
||||
"settings.oauth_applications_disabled": "OAuth applications are disabled in <code>m8sh</code>, use personal access token."
|
||||
}
|
||||
@@ -2974,5 +2974,6 @@
|
||||
"git.filemode.normal_file": "Archivo normal",
|
||||
"git.filemode.executable_file": "Archivo ejecutable",
|
||||
"git.filemode.symbolic_link": "Enlace simbólico",
|
||||
"git.filemode.submodule": "Submódulo"
|
||||
"git.filemode.submodule": "Submódulo",
|
||||
"settings.oauth_applications_disabled": "Las aplicaciones OAuth están deshabilitadas en <code>m8sh</code>, usa un token de acceso personal."
|
||||
}
|
||||
@@ -2243,5 +2243,6 @@
|
||||
"gpg.signup.paste_key": "کلید عمومی GPG خود را جایگذاری کنید",
|
||||
"gpg.signup.proceed": "ادامه",
|
||||
"gpg.signup.submit": "تأیید و ایجاد حساب",
|
||||
"gpg.signup.already_have_account": "قبلاً حساب دارید؟"
|
||||
"gpg.signup.already_have_account": "قبلاً حساب دارید؟",
|
||||
"settings.oauth_applications_disabled": "برنامههای OAuth در <code>m8sh</code> غیرفعال هستند، از توکن دسترسی شخصی استفاده کنید."
|
||||
}
|
||||
@@ -1489,5 +1489,6 @@
|
||||
"gpg.signup.paste_key": "Liitä GPG-julkinen avaimesi",
|
||||
"gpg.signup.proceed": "Jatka",
|
||||
"gpg.signup.submit": "Vahvista ja luo tili",
|
||||
"gpg.signup.already_have_account": "Onko sinulla jo tili?"
|
||||
"gpg.signup.already_have_account": "Onko sinulla jo tili?",
|
||||
"settings.oauth_applications_disabled": "OAuth-sovellukset on poistettu käytöstä <code>m8sh</code>-järjestelmässä, käytä henkilökohtaista käyttöoikeustunnusta."
|
||||
}
|
||||
@@ -3880,5 +3880,6 @@
|
||||
"actions.general.cross_repo_desc": "Permet aux dépôts sélectionnés d’être visible en lecture-seule par tous les dépôts de ce propriétaire à l’aide de GITEA_TOKEN lors de l’exécution des tâches d’actions.",
|
||||
"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"
|
||||
"actions.general.cross_repo_add": "Ajouter un dépôt cible",
|
||||
"settings.oauth_applications_disabled": "Les applications OAuth sont désactivées dans <code>m8sh</code>, utilisez un jeton d'accès personnel."
|
||||
}
|
||||
@@ -3894,5 +3894,6 @@
|
||||
"gpg.signup.paste_key": "Greamaigh d'eochair phoiblí GPG",
|
||||
"gpg.signup.proceed": "Ar aghaidh",
|
||||
"gpg.signup.submit": "Fíoraigh & cruthaigh cuntas",
|
||||
"gpg.signup.already_have_account": "An bhfuil cuntas agat cheana?"
|
||||
"gpg.signup.already_have_account": "An bhfuil cuntas agat cheana?",
|
||||
"settings.oauth_applications_disabled": "Tá feidhmchláir OAuth díchumasaithe i <code>m8sh</code>, bain úsáid as comhartha rochtana pearsanta."
|
||||
}
|
||||
@@ -1398,5 +1398,6 @@
|
||||
"gpg.signup.paste_key": "Illeszd be a GPG nyilvános kulcsodat",
|
||||
"gpg.signup.proceed": "Tovább",
|
||||
"gpg.signup.submit": "Ellenőrzés & fiók létrehozása",
|
||||
"gpg.signup.already_have_account": "Már van fiókod?"
|
||||
"gpg.signup.already_have_account": "Már van fiókod?",
|
||||
"settings.oauth_applications_disabled": "Az OAuth-alkalmazások le vannak tiltva a <code>m8sh</code>-ban, használjon személyes hozzáférési tokent."
|
||||
}
|
||||
@@ -1213,5 +1213,6 @@
|
||||
"gpg.signup.paste_key": "Tempel kunci publik GPG Anda",
|
||||
"gpg.signup.proceed": "Lanjutkan",
|
||||
"gpg.signup.submit": "Verifikasi & buat akun",
|
||||
"gpg.signup.already_have_account": "Sudah punya akun?"
|
||||
"gpg.signup.already_have_account": "Sudah punya akun?",
|
||||
"settings.oauth_applications_disabled": "Aplikasi OAuth dinonaktifkan di <code>m8sh</code>, gunakan token akses pribadi."
|
||||
}
|
||||
@@ -1131,5 +1131,6 @@
|
||||
"gpg.signup.paste_key": "Límdu GPG opinbera lykilinn þinn",
|
||||
"gpg.signup.proceed": "Halda áfram",
|
||||
"gpg.signup.submit": "Staðfesta & búa til reikning",
|
||||
"gpg.signup.already_have_account": "Áttu nú þegar reikning?"
|
||||
"gpg.signup.already_have_account": "Áttu nú þegar reikning?",
|
||||
"settings.oauth_applications_disabled": "OAuth forrit eru óvirk í <code>m8sh</code>, notaðu persónulegan aðgangstákn."
|
||||
}
|
||||
@@ -2392,5 +2392,6 @@
|
||||
"gpg.signup.paste_key": "Incolla la tua chiave pubblica GPG",
|
||||
"gpg.signup.proceed": "Continua",
|
||||
"gpg.signup.submit": "Verifica e crea account",
|
||||
"gpg.signup.already_have_account": "Hai già un account?"
|
||||
"gpg.signup.already_have_account": "Hai già un account?",
|
||||
"settings.oauth_applications_disabled": "Le applicazioni OAuth sono disabilitate in <code>m8sh</code>, usa un token di accesso personale."
|
||||
}
|
||||
@@ -3865,5 +3865,6 @@
|
||||
"actions.general.cross_repo_desc": "Actionsジョブの実行時に、このオーナーのすべてのリポジトリから選択したリポジトリへ、GITEA_TOKENを使用して(読み取り専用で)アクセスすることを許可します。",
|
||||
"actions.general.cross_repo_selected": "選択したリポジトリ",
|
||||
"actions.general.cross_repo_target_repos": "対象リポジトリ",
|
||||
"actions.general.cross_repo_add": "対象リポジトリの追加"
|
||||
"actions.general.cross_repo_add": "対象リポジトリの追加",
|
||||
"settings.oauth_applications_disabled": "<code>m8sh</code> では OAuth アプリケーションは無効です。個人用アクセストークンを使用してください。"
|
||||
}
|
||||
@@ -3893,5 +3893,6 @@
|
||||
"actions.general.cross_repo_desc": "선택된 저장소가 이 소유자의 모든 저장소에서 액션 작업을 실행할 때 GITEA_TOKEN을 사용하여 (읽기 전용으로) 액세스할 수 있도록 허용합니다.",
|
||||
"actions.general.cross_repo_selected": "선택된 리포지토리",
|
||||
"actions.general.cross_repo_target_repos": "대상 리포지토리",
|
||||
"actions.general.cross_repo_add": "대상 리포지토리 추가"
|
||||
"actions.general.cross_repo_add": "대상 리포지토리 추가",
|
||||
"settings.oauth_applications_disabled": "<code>m8sh</code>에서 OAuth 애플리케이션이 비활성화되었습니다. 개인 액세스 토큰을 사용하세요."
|
||||
}
|
||||
@@ -3057,5 +3057,6 @@
|
||||
"gpg.signup.paste_key": "Ielīmējiet savu GPG publisko atslēgu",
|
||||
"gpg.signup.proceed": "Turpināt",
|
||||
"gpg.signup.submit": "Verificēt un izveidot kontu",
|
||||
"gpg.signup.already_have_account": "Jums jau ir konts?"
|
||||
"gpg.signup.already_have_account": "Jums jau ir konts?",
|
||||
"settings.oauth_applications_disabled": "OAuth lietotnes ir atspējotas <code>m8sh</code>, izmantojiet personīgo piekļuves tokenu."
|
||||
}
|
||||
@@ -2108,5 +2108,6 @@
|
||||
"gpg.signup.paste_key": "Plak je GPG publieke sleutel",
|
||||
"gpg.signup.proceed": "Doorgaan",
|
||||
"gpg.signup.submit": "Verifiëren & account aanmaken",
|
||||
"gpg.signup.already_have_account": "Heb je al een account?"
|
||||
"gpg.signup.already_have_account": "Heb je al een account?",
|
||||
"settings.oauth_applications_disabled": "OAuth-applicaties zijn uitgeschakeld in <code>m8sh</code>, gebruik een persoonlijk toegangstoken."
|
||||
}
|
||||
@@ -2125,5 +2125,6 @@
|
||||
"gpg.signup.paste_key": "Wklej swój publiczny klucz GPG",
|
||||
"gpg.signup.proceed": "Kontynuuj",
|
||||
"gpg.signup.submit": "Zweryfikuj i utwórz konto",
|
||||
"gpg.signup.already_have_account": "Masz już konto?"
|
||||
"gpg.signup.already_have_account": "Masz już konto?",
|
||||
"settings.oauth_applications_disabled": "Aplikacje OAuth są wyłączone w <code>m8sh</code>, użyj osobistego tokena dostępu."
|
||||
}
|
||||
@@ -3301,5 +3301,6 @@
|
||||
"gpg.signup.paste_key": "Cole sua chave pública GPG",
|
||||
"gpg.signup.proceed": "Continuar",
|
||||
"gpg.signup.submit": "Verificar e criar conta",
|
||||
"gpg.signup.already_have_account": "Já tem uma conta?"
|
||||
"gpg.signup.already_have_account": "Já tem uma conta?",
|
||||
"settings.oauth_applications_disabled": "Aplicativos OAuth estão desabilitados no <code>m8sh</code>, use um token de acesso pessoal."
|
||||
}
|
||||
@@ -3739,5 +3739,6 @@
|
||||
"gpg.signup.paste_key": "Cole a sua chave pública GPG",
|
||||
"gpg.signup.proceed": "Continuar",
|
||||
"gpg.signup.submit": "Verificar e criar conta",
|
||||
"gpg.signup.already_have_account": "Já tem uma conta?"
|
||||
"gpg.signup.already_have_account": "Já tem uma conta?",
|
||||
"settings.oauth_applications_disabled": "As aplicações OAuth estão desativadas em <code>m8sh</code>, utilize um token de acesso pessoal."
|
||||
}
|
||||
@@ -309,7 +309,7 @@
|
||||
"auth.invalid_password": "Ваш пароль не совпадает с паролем, который был использован для создания учётной записи.",
|
||||
"auth.gpg_invalid_token_signature": "Неверная GPG-подпись или nonce",
|
||||
"auth.invalid_gpg_identity": "Не удалось извлечь идентификатор из GPG-ключа",
|
||||
"auth.invalid_gpg_key_email": "Не удалось извлечь адрес электронной почты из GPG-ключа",
|
||||
"auth.invalid_gpg_key_email": "Не удалось извлечь адрес электронной почты из GPG-ключа",
|
||||
"auth.reset_password_helper": "Восстановить аккаунт",
|
||||
"auth.reset_password_wrong_user": "Вы вошли как %s, но ссылка для восстановления учётной записи предназначена для %s",
|
||||
"auth.password_too_short": "Пароль не может быть короче %d символов.",
|
||||
@@ -2997,5 +2997,6 @@
|
||||
"git.filemode.normal_file": "Обычный файл",
|
||||
"git.filemode.executable_file": "Исполняемый файл",
|
||||
"git.filemode.symbolic_link": "Символическая ссылка",
|
||||
"git.filemode.submodule": "Подмодуль"
|
||||
"git.filemode.submodule": "Подмодуль",
|
||||
"settings.oauth_applications_disabled": "Приложения OAuth отключены в <code>m8sh</code>, используйте персональный токен доступа."
|
||||
}
|
||||
@@ -2204,5 +2204,6 @@
|
||||
"gpg.signup.paste_key": "ඔබේ GPG පොදු යතුර අලවන්න",
|
||||
"gpg.signup.proceed": "ඉදිරියට යන්න",
|
||||
"gpg.signup.submit": "තහවුරු කර ගිණුම සාදන්න",
|
||||
"gpg.signup.already_have_account": "දැනටමත් ගිණුමක් තිබේද?"
|
||||
"gpg.signup.already_have_account": "දැනටමත් ගිණුමක් තිබේද?",
|
||||
"settings.oauth_applications_disabled": "<code>m8sh</code> හි OAuth යෙදුම් අක්රිය කර ඇත, පෞද්ගලික ප්රවේශ ටෝකනය භාවිතා කරන්න."
|
||||
}
|
||||
@@ -1175,5 +1175,6 @@
|
||||
"gpg.signup.paste_key": "Vložte váš verejný GPG kľúč",
|
||||
"gpg.signup.proceed": "Pokračovať",
|
||||
"gpg.signup.submit": "Overiť a vytvoriť účet",
|
||||
"gpg.signup.already_have_account": "Už máte účet?"
|
||||
"gpg.signup.already_have_account": "Už máte účet?",
|
||||
"settings.oauth_applications_disabled": "OAuth aplikácie sú v <code>m8sh</code> zakázané, použite osobný prístupový token."
|
||||
}
|
||||
@@ -1748,5 +1748,6 @@
|
||||
"gpg.signup.paste_key": "Klistra in din GPG publika nyckel",
|
||||
"gpg.signup.proceed": "Fortsätt",
|
||||
"gpg.signup.submit": "Verifiera och skapa konto",
|
||||
"gpg.signup.already_have_account": "Har du redan ett konto?"
|
||||
"gpg.signup.already_have_account": "Har du redan ett konto?",
|
||||
"settings.oauth_applications_disabled": "OAuth-applikationer är inaktiverade i <code>m8sh</code>, använd personlig åtkomsttoken."
|
||||
}
|
||||
@@ -3758,5 +3758,6 @@
|
||||
"gpg.signup.paste_key": "GPG açık anahtarınızı yapıştırın",
|
||||
"gpg.signup.proceed": "Devam et",
|
||||
"gpg.signup.submit": "Doğrula ve hesap oluştur",
|
||||
"gpg.signup.already_have_account": "Zaten hesabınız var mı?"
|
||||
"gpg.signup.already_have_account": "Zaten hesabınız var mı?",
|
||||
"settings.oauth_applications_disabled": "<code>m8sh</code> içinde OAuth uygulamaları devre dışı bırakıldı, kişisel erişim tokeni kullanın."
|
||||
}
|
||||
@@ -3197,5 +3197,6 @@
|
||||
"gpg.signup.paste_key": "Вставте ваш публічний GPG ключ",
|
||||
"gpg.signup.proceed": "Продовжити",
|
||||
"gpg.signup.submit": "Підтвердити та створити акаунт",
|
||||
"gpg.signup.already_have_account": "Вже маєте акаунт?"
|
||||
"gpg.signup.already_have_account": "Вже маєте акаунт?",
|
||||
"settings.oauth_applications_disabled": "Програми OAuth вимкнено в <code>m8sh</code>, використовуйте персональний токен доступу."
|
||||
}
|
||||
@@ -3902,5 +3902,6 @@
|
||||
"actions.general.cross_repo_desc": "允许此所有者下的所有仓库在运行工作流任务时,通过 GITEA_TOKEN 以只读方式访问所选仓库。",
|
||||
"actions.general.cross_repo_selected": "选择的仓库",
|
||||
"actions.general.cross_repo_target_repos": "目标仓库",
|
||||
"actions.general.cross_repo_add": "添加目标仓库"
|
||||
"actions.general.cross_repo_add": "添加目标仓库",
|
||||
"settings.oauth_applications_disabled": "<code>m8sh</code> 中已禁用 OAuth 应用,请使用个人访问令牌。"
|
||||
}
|
||||
@@ -3312,5 +3312,6 @@
|
||||
"gpg.signup.paste_key": "貼上您的 GPG 公開金鑰",
|
||||
"gpg.signup.proceed": "繼續",
|
||||
"gpg.signup.submit": "驗證並建立帳戶",
|
||||
"gpg.signup.already_have_account": "已有帳戶?"
|
||||
"gpg.signup.already_have_account": "已有帳戶?",
|
||||
"settings.oauth_applications_disabled": "<code>m8sh</code> 中已停用 OAuth 應用程式,請使用個人存取權杖。"
|
||||
}
|
||||
@@ -4,7 +4,9 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
"gitea.dev/models/db"
|
||||
@@ -136,12 +138,25 @@ func CreateGPGKey(ctx *context.APIContext) {
|
||||
|
||||
// DeleteGPGKey remove a GPG key belonging to the authenticated user
|
||||
func DeleteGPGKey(ctx *context.APIContext) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||
ctx.APIErrorNotFound("gpg keys setting is not allowed to be changed")
|
||||
keyID := ctx.PathParamInt64("id")
|
||||
|
||||
key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, keyID)
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrGPGKeyNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.PathParamInt64("id")); err != nil {
|
||||
// m8sh: Protect last key for primary registration email
|
||||
if err := checkLastPrimaryEmailKey(ctx, ctx.Doer, key); err != nil {
|
||||
ctx.APIError(http.StatusForbidden, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, keyID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
@@ -149,6 +164,56 @@ func DeleteGPGKey(ctx *context.APIContext) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// checkLastPrimaryEmailKey returns error if deleting this key would remove
|
||||
// the last remaining key (including its subkeys) that contains the primary email.
|
||||
func checkLastPrimaryEmailKey(ctx *context.APIContext, user *user_model.User, deletingKey *asymkey_model.GPGKey) error {
|
||||
primaryEmail := user.Email
|
||||
|
||||
if !keyContainsEmail(deletingKey, primaryEmail) {
|
||||
return nil // safe to delete
|
||||
}
|
||||
|
||||
allKeys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
|
||||
OwnerID: user.ID,
|
||||
IncludeSubKeys: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load keys")
|
||||
}
|
||||
|
||||
count := 0
|
||||
for _, k := range allKeys {
|
||||
if keyContainsEmail(k, primaryEmail) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
if count <= 1 {
|
||||
return fmt.Errorf("cannot delete the last GPG key for your primary email (%s)", primaryEmail)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyContainsEmail checks if a GPG key (or any of its subkeys) contains the given email
|
||||
func keyContainsEmail(key *asymkey_model.GPGKey, email string) bool {
|
||||
for _, e := range key.Emails {
|
||||
if strings.EqualFold(e.Email, email) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, sub := range key.SubsKey {
|
||||
for _, e := range sub.Emails {
|
||||
if strings.EqualFold(e.Email, email) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HandleAddGPGKeyError handle add GPGKey error
|
||||
func HandleAddGPGKeyError(ctx *context.APIContext, err error, nonce string) {
|
||||
switch {
|
||||
|
||||
@@ -963,6 +963,27 @@ func SignInGPGPost(ctx *context.Context) {
|
||||
user, err := asymkey_model.VerifyGPGSignature(ctx, form.Nonce, form.Signature)
|
||||
if err != nil {
|
||||
ctx.Data["Err_GPGSign"] = true
|
||||
ctx.Data["Flash"] = &middleware.Flash{
|
||||
ErrorMsg: ctx.Locale.TrString("auth.gpg_invalid_token_signature"),
|
||||
}
|
||||
ctx.HTML(http.StatusOK, tplSignIn)
|
||||
return
|
||||
}
|
||||
|
||||
if !user.IsActive {
|
||||
ctx.Data["Err_GPGSign"] = true
|
||||
ctx.Data["Flash"] = &middleware.Flash{
|
||||
ErrorMsg: ctx.Locale.TrString("auth.active_your_account"),
|
||||
}
|
||||
ctx.HTML(http.StatusOK, tplSignIn)
|
||||
return
|
||||
}
|
||||
|
||||
if user.ProhibitLogin {
|
||||
ctx.Data["Err_GPGSign"] = true
|
||||
ctx.Data["Flash"] = &middleware.Flash{
|
||||
ErrorMsg: ctx.Locale.TrString("auth.prohibit_login"),
|
||||
}
|
||||
ctx.HTML(http.StatusOK, tplSignIn)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -182,7 +182,8 @@ func AuthorizeOAuth(ctx *context.Context) {
|
||||
if len(errs) > 0 {
|
||||
var errstring strings.Builder
|
||||
for _, e := range errs {
|
||||
errstring.WriteString(e.Error() + "\n")
|
||||
errstring.WriteString(e.Error())
|
||||
errstring.WriteString("\n")
|
||||
}
|
||||
ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring.String()))
|
||||
return
|
||||
|
||||
+2
-86
@@ -5,9 +5,8 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
@@ -22,98 +21,15 @@ import (
|
||||
"gitea.dev/modules/storage"
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/agit"
|
||||
asymkey_service "gitea.dev/services/asymkey"
|
||||
org_service "gitea.dev/services/org"
|
||||
"gitea.dev/services/packages"
|
||||
container_service "gitea.dev/services/packages/container"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
// RenameUser renames a user
|
||||
func RenameUser(ctx context.Context, u *user_model.User, newUserName string, doer *user_model.User) error {
|
||||
if newUserName == u.Name {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Non-local users are not allowed to change their own username, but admins are
|
||||
isExternalUser := !u.IsOrganization() && !u.IsLocal()
|
||||
if isExternalUser && !doer.IsAdmin {
|
||||
return user_model.ErrUserIsNotLocal{UID: u.ID, Name: u.Name}
|
||||
}
|
||||
|
||||
if err := user_model.IsUsableUsername(newUserName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
onlyCapitalization := strings.EqualFold(newUserName, u.Name)
|
||||
oldUserName := u.Name
|
||||
|
||||
if onlyCapitalization {
|
||||
u.Name = newUserName
|
||||
if err := user_model.UpdateUserCols(ctx, u, "name"); err != nil {
|
||||
u.Name = oldUserName
|
||||
return err
|
||||
}
|
||||
return repo_model.UpdateRepositoryOwnerNames(ctx, u.ID, newUserName)
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
isExist, err := user_model.IsUserExist(ctx, u.ID, newUserName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isExist {
|
||||
return user_model.ErrUserAlreadyExist{
|
||||
Name: newUserName,
|
||||
}
|
||||
}
|
||||
|
||||
if err = repo_model.UpdateRepositoryOwnerName(ctx, oldUserName, newUserName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = user_model.NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := agit.UserNameChanged(ctx, u, newUserName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container_service.UpdateRepositoryNames(ctx, u, newUserName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Name = newUserName
|
||||
u.LowerName = strings.ToLower(newUserName)
|
||||
if err := user_model.UpdateUserCols(ctx, u, "name", "lower_name"); err != nil {
|
||||
u.Name = oldUserName
|
||||
u.LowerName = strings.ToLower(oldUserName)
|
||||
return err
|
||||
}
|
||||
|
||||
// Do not fail if directory does not exist
|
||||
if err = util.Rename(user_model.UserPath(oldUserName), user_model.UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
|
||||
u.Name = oldUserName
|
||||
u.LowerName = strings.ToLower(oldUserName)
|
||||
return fmt.Errorf("rename user directory: %w", err)
|
||||
}
|
||||
|
||||
if err = committer.Commit(); err != nil {
|
||||
u.Name = oldUserName
|
||||
u.LowerName = strings.ToLower(oldUserName)
|
||||
if err2 := util.Rename(user_model.UserPath(newUserName), user_model.UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
|
||||
log.Error("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2)
|
||||
return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return errors.New("username change is disabled in m8sh")
|
||||
}
|
||||
|
||||
// DeleteUser completely and permanently deletes everything of a user,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
{{template "org/settings/layout_head" (dict "pageClass" "organization settings options")}}
|
||||
<div class="org-setting-content">
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "settings.applications"}}
|
||||
</h4>
|
||||
|
||||
{{template "user/settings/applications_oauth2_list" .}}
|
||||
</div>
|
||||
{{template "org/settings/layout_footer" .}}
|
||||
<div class="org-setting-content">
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "settings.applications"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<div class="ui info">
|
||||
<p>{{ctx.Locale.Tr "settings.oauth_applications_disabled"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "org/settings/layout_footer" .}}
|
||||
@@ -1,6 +1 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "settings.manage_oauth2_applications"}}
|
||||
</h4>
|
||||
|
||||
{{template "user/settings/applications_oauth2_list" .}}
|
||||
|
||||
{{/* OAuth disabled */}}
|
||||
@@ -1,57 +1 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "settings.edit_oauth2_application"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<p>{{ctx.Locale.Tr "settings.oauth2_application_create_description"}}</p>
|
||||
</div>
|
||||
<div class="ui attached segment form ignore-dirty">
|
||||
<div class="field">
|
||||
<label for="client-id">{{ctx.Locale.Tr "settings.oauth2_client_id"}}</label>
|
||||
<input id="client-id" readonly value="{{.App.ClientID}}">
|
||||
</div>
|
||||
{{if .ClientSecret}}
|
||||
<div class="field">
|
||||
<label for="client-secret">{{ctx.Locale.Tr "settings.oauth2_client_secret"}}</label>
|
||||
<input id="client-secret" type="text" readonly value="{{.ClientSecret}}">
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="field">
|
||||
<label for="client-secret">{{ctx.Locale.Tr "settings.oauth2_client_secret"}}</label>
|
||||
<input id="client-secret" type="password" readonly value="averysecuresecret">
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="item">
|
||||
<!-- TODO add regenerate secret functionality */ -->
|
||||
<form class="ui form ignore-dirty" action="{{.FormActionPath}}/regenerate_secret" method="post">
|
||||
{{ctx.Locale.Tr "settings.oauth2_regenerate_secret_hint"}}
|
||||
<button class="ui mini button tw-ml-2" type="submit">{{ctx.Locale.Tr "settings.oauth2_regenerate_secret"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui bottom attached segment">
|
||||
<form class="ui form ignore-dirty" action="{{.FormActionPath}}" method="post">
|
||||
<div class="field {{if .Err_AppName}}error{{end}}">
|
||||
<label for="application-name">{{ctx.Locale.Tr "settings.oauth2_application_name"}}</label>
|
||||
<input id="application-name" value="{{.App.Name}}" name="application_name" required maxlength="255">
|
||||
</div>
|
||||
<div class="field {{if .Err_RedirectURI}}error{{end}}">
|
||||
<label for="redirect-uris">{{ctx.Locale.Tr "settings.oauth2_redirect_uris"}}</label>
|
||||
<textarea name="redirect_uris" id="redirect-uris" required>{{StringUtils.Join .App.RedirectURIs "\n"}}</textarea>
|
||||
</div>
|
||||
<div class="field {{if .Err_ConfidentialClient}}error{{end}}">
|
||||
<div class="ui checkbox">
|
||||
<label>{{ctx.Locale.Tr "settings.oauth2_confidential_client"}}</label>
|
||||
<input class="disable-setting" type="checkbox" name="confidential_client" data-target="#skip-secondary-authorization" {{if .App.ConfidentialClient}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field {{if .Err_SkipSecondaryAuthorization}}error{{end}} {{if .App.ConfidentialClient}}disabled{{end}}" id="skip-secondary-authorization">
|
||||
<div class="ui checkbox">
|
||||
<label>{{ctx.Locale.Tr "settings.oauth2_skip_secondary_authorization"}}</label>
|
||||
<input type="checkbox" name="skip_secondary_authorization" {{if .App.SkipSecondaryAuthorization}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui primary button">
|
||||
{{ctx.Locale.Tr "settings.save_application"}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{{/* OAuth disabled */}}
|
||||
@@ -1,79 +1 @@
|
||||
<div class="ui attached segment">
|
||||
<div class="flex-divided-list items-with-main">
|
||||
<div class="item">
|
||||
{{ctx.Locale.Tr "settings.oauth2_application_create_description"}}
|
||||
</div>
|
||||
{{range .Applications}}
|
||||
<div class="item tw-items-center">
|
||||
<div class="item-leading">
|
||||
{{svg "octicon-apps" 32}}
|
||||
</div>
|
||||
<div class="item-main">
|
||||
<div class="item-title">{{.Name}}</div>
|
||||
<div class="item-body">
|
||||
{{ctx.Locale.Tr "settings.oauth2_client_id"}}
|
||||
<span class="ui label">{{.ClientID}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{$isBuiltin := and $.BuiltinApplications (index $.BuiltinApplications .ClientID)}}
|
||||
<div class="item-trailing">
|
||||
{{if $isBuiltin}}
|
||||
<span class="ui basic label" data-tooltip-content="{{ctx.Locale.Tr "settings.oauth2_application_locked"}}">{{ctx.Locale.Tr "locked"}}</span>
|
||||
{{else}}
|
||||
<a href="{{$.Link}}/oauth2/{{.ID}}" class="ui primary tiny button">
|
||||
{{svg "octicon-pencil"}}
|
||||
{{ctx.Locale.Tr "settings.oauth2_application_edit"}}
|
||||
</a>
|
||||
<button class="ui red tiny button delete-button" data-modal-id="remove-gitea-oauth2-application"
|
||||
data-url="{{$.Link}}/oauth2/{{.ID}}/delete">
|
||||
{{svg "octicon-trash"}}
|
||||
{{ctx.Locale.Tr "settings.delete_key"}}
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="ui g-modal-confirm delete modal" id="remove-gitea-oauth2-application">
|
||||
<div class="header">
|
||||
{{svg "octicon-trash"}}
|
||||
{{ctx.Locale.Tr "settings.remove_oauth2_application"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "settings.oauth2_application_remove_description"}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached segment">
|
||||
<details {{if .application_name}}open{{end}}>
|
||||
<summary><h4 class="ui header tw-inline-block tw-my-2">{{ctx.Locale.Tr "settings.create_oauth2_application"}}</h4></summary>
|
||||
<form class="ui form ignore-dirty" action="{{.Link}}/oauth2" method="post">
|
||||
<div class="field {{if .Err_AppName}}error{{end}}">
|
||||
<label for="application-name">{{ctx.Locale.Tr "settings.oauth2_application_name"}}</label>
|
||||
<input id="application-name" name="application_name" value="{{.application_name}}" required maxlength="255">
|
||||
</div>
|
||||
<div class="field {{if .Err_RedirectURI}}error{{end}}">
|
||||
<label for="redirect-uris">{{ctx.Locale.Tr "settings.oauth2_redirect_uris"}}</label>
|
||||
<textarea name="redirect_uris" id="redirect-uris"></textarea>
|
||||
</div>
|
||||
<div class="field {{if .Err_ConfidentialClient}}error{{end}}">
|
||||
<div class="ui checkbox">
|
||||
<label>{{ctx.Locale.Tr "settings.oauth2_confidential_client"}}</label>
|
||||
<input class="disable-setting" type="checkbox" name="confidential_client" data-target="#skip-secondary-authorization" checked>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field {{if .Err_SkipSecondaryAuthorization}}error{{end}} disabled" id="skip-secondary-authorization">
|
||||
<div class="ui checkbox">
|
||||
<label>{{ctx.Locale.Tr "settings.oauth2_skip_secondary_authorization"}}</label>
|
||||
<input type="checkbox" name="skip_secondary_authorization">
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui primary button">
|
||||
{{ctx.Locale.Tr "settings.create_oauth2_application_button"}}
|
||||
</button>
|
||||
</form>
|
||||
</details>
|
||||
</div>
|
||||
{{/* OAuth2 applications completely disabled in m8sh */}}
|
||||
@@ -1,40 +1 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "settings.authorized_oauth2_applications"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<div class="flex-divided-list items-with-main">
|
||||
<div class="item">
|
||||
{{ctx.Locale.Tr "settings.authorized_oauth2_applications_description"}}
|
||||
</div>
|
||||
{{range .Grants}}
|
||||
<div class="item">
|
||||
<div class="item-leading">
|
||||
{{svg "octicon-key" 32}}
|
||||
</div>
|
||||
<div class="item-main">
|
||||
<div class="item-title">{{.Application.Name}}</div>
|
||||
<div class="item-body">
|
||||
<i>{{ctx.Locale.Tr "settings.added_on" (DateUtils.AbsoluteShort .CreatedUnix)}}</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-trailing">
|
||||
<button class="ui red tiny button delete-button" data-modal-id="revoke-gitea-oauth2-grant"
|
||||
data-url="{{AppSubUrl}}/user/settings/applications/oauth2/{{.ApplicationID}}/revoke/{{.ID}}">
|
||||
{{ctx.Locale.Tr "settings.revoke_key"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="ui g-modal-confirm delete modal" id="revoke-gitea-oauth2-grant">
|
||||
<div class="header">
|
||||
{{svg "octicon-shield" 16 "tw-mr-1"}}
|
||||
{{ctx.Locale.Tr "settings.revoke_oauth2_grant"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "settings.revoke_oauth2_grant_description"}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</div>
|
||||
</div>
|
||||
{{/* OAuth2 grants completely disabled in m8sh (GPG auth only) */}}
|
||||
@@ -5,9 +5,9 @@
|
||||
{{ctx.Locale.Tr "settings.profile"}}
|
||||
</a>
|
||||
{{if not ($.UserDisabledFeatures.Contains "manage_credentials" "deletion")}}
|
||||
<a class="{{if .PageIsSettingsAccount}}active {{end}}item" href="{{AppSubUrl}}/user/settings/account">
|
||||
<!-- <a class="{{if .PageIsSettingsAccount}}active {{end}}item" href="{{AppSubUrl}}/user/settings/account">
|
||||
{{ctx.Locale.Tr "settings.account"}}
|
||||
</a>
|
||||
</a> m8sh doesn't support email changes/other account related actions except deletion -->
|
||||
{{end}}
|
||||
{{if $.EnableNotifyMail}}
|
||||
<a class="{{if .PageIsSettingsNotifications}}active {{end}}item" href="{{AppSubUrl}}/user/settings/notifications">
|
||||
|
||||
@@ -6,22 +6,13 @@
|
||||
<div class="ui attached segment">
|
||||
<p>{{ctx.Locale.Tr "settings.profile_desc"}}</p>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<div class="required field {{if .Err_Name}}error{{end}}">
|
||||
<label for="username">{{ctx.Locale.Tr "username"}}
|
||||
<span class="tw-text-red tw-hidden" id="name-change-prompt"> {{ctx.Locale.Tr "settings.change_username_prompt"}}</span>
|
||||
<span class="tw-text-red tw-hidden" id="name-change-redirect-prompt"> {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}</span>
|
||||
</label>
|
||||
<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" required {{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}disabled{{end}} maxlength="40">
|
||||
{{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}
|
||||
<p class="help tw-text-blue">{{ctx.Locale.Tr "settings.password_username_disabled"}}</p>
|
||||
{{end}}
|
||||
<div class="required field">
|
||||
<label for="username">{{ctx.Locale.Tr "username"}}</label>
|
||||
<input id="username" name="name" value="{{.SignedUser.Name}}" disabled maxlength="40">
|
||||
</div>
|
||||
<div class="field {{if .Err_FullName}}error{{end}}">
|
||||
<label for="full_name">{{ctx.Locale.Tr "settings.full_name"}}</label>
|
||||
<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" {{if ($.UserDisabledFeatures.Contains "change_full_name")}}disabled{{end}} maxlength="100">
|
||||
{{if ($.UserDisabledFeatures.Contains "change_full_name")}}
|
||||
<p class="help tw-text-blue">{{ctx.Locale.Tr "settings.password_full_name_disabled"}}</p>
|
||||
{{end}}
|
||||
<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" maxlength="100">
|
||||
</div>
|
||||
<div class="field {{if .Err_Email}}error{{end}}">
|
||||
<label>{{ctx.Locale.Tr "email"}}</label>
|
||||
|
||||
@@ -1,58 +1,16 @@
|
||||
export function generateNonce(): string {
|
||||
const existing = sessionStorage.getItem('gpg_signup_nonce');
|
||||
if (existing) return existing;
|
||||
|
||||
export function makeNonce(): string {
|
||||
const ts = Math.floor(Date.now() / 1000).toString(16).padStart(8, '0');
|
||||
const arr = new Uint8Array(28);
|
||||
crypto.getRandomValues(arr);
|
||||
const random = Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join('');
|
||||
const nonce = ts + random;
|
||||
sessionStorage.setItem('gpg_signup_nonce', nonce);
|
||||
return nonce;
|
||||
return ts + Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
export function buildSignCommand(nonce: string, keyID?: string): string {
|
||||
if (keyID) {
|
||||
return `echo "${nonce}" | gpg -a --default-key ${keyID} --detach-sig`;
|
||||
}
|
||||
if (keyID) return `echo "${nonce}" | gpg -a --default-key ${keyID} --detach-sig`;
|
||||
return `echo "${nonce}" | gpg -a --detach-sig`;
|
||||
}
|
||||
|
||||
export function validateSignature(sig: string): boolean {
|
||||
return sig.startsWith('-----BEGIN PGP SIGNED MESSAGE-----') ||
|
||||
sig.startsWith('-----BEGIN PGP SIGNATURE-----');
|
||||
}
|
||||
|
||||
export function initGpgNonceWidget(opts: {
|
||||
tokenFieldId: string;
|
||||
nonceInputId: string;
|
||||
signCommandId: string;
|
||||
copyBtnId: string;
|
||||
signatureId: string;
|
||||
formId: string;
|
||||
keyID?: string;
|
||||
}): string | null {
|
||||
const tokenField = document.querySelector(opts.tokenFieldId);
|
||||
if (!tokenField) return null;
|
||||
|
||||
const nonce = generateNonce();
|
||||
const cmd = buildSignCommand(nonce, opts.keyID);
|
||||
|
||||
(tokenField as HTMLInputElement).value = nonce;
|
||||
(document.querySelector(opts.nonceInputId) as HTMLInputElement).value = nonce;
|
||||
(document.querySelector(opts.signCommandId) as HTMLInputElement).value = cmd;
|
||||
|
||||
document.querySelector(opts.copyBtnId)?.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(cmd);
|
||||
});
|
||||
|
||||
document.querySelector(opts.formId)?.addEventListener('submit', (e) => {
|
||||
const sig = (document.querySelector(opts.signatureId) as HTMLTextAreaElement).value.trim();
|
||||
if (!validateSignature(sig)) {
|
||||
e.preventDefault();
|
||||
alert('Paste the GPG signed output.');
|
||||
}
|
||||
});
|
||||
|
||||
return nonce;
|
||||
sig.startsWith('-----BEGIN PGP SIGNATURE-----');
|
||||
}
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
import { buildSignCommand, validateSignature } from './gpg-nonce.ts';
|
||||
import {makeNonce, buildSignCommand, validateSignature} from './gpg-nonce.ts';
|
||||
|
||||
export function initGpgSignin() {
|
||||
const tokenField = document.getElementById('token-field') as HTMLInputElement;
|
||||
const tokenField = document.querySelector('token-field') as HTMLInputElement;
|
||||
if (!tokenField) return;
|
||||
|
||||
const ts = Math.floor(Date.now() / 1000).toString(16).padStart(8, '0');
|
||||
const arr = new Uint8Array(28);
|
||||
crypto.getRandomValues(arr);
|
||||
const random = Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join('');
|
||||
const nonce = ts + random;
|
||||
|
||||
const nonce = makeNonce();
|
||||
const cmd = buildSignCommand(nonce);
|
||||
|
||||
tokenField.value = nonce;
|
||||
(document.getElementById('hidden-nonce') as HTMLInputElement).value = nonce;
|
||||
(document.getElementById('sign-command') as HTMLInputElement).value = cmd;
|
||||
(document.querySelector('hidden-nonce') as HTMLInputElement).value = nonce;
|
||||
(document.querySelector('sign-command') as HTMLInputElement).value = cmd;
|
||||
|
||||
document.getElementById('btn-copy-cmd')?.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(cmd);
|
||||
document.querySelector('btn-copy-cmd')?.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(
|
||||
(document.querySelector('sign-command') as HTMLInputElement).value,
|
||||
);
|
||||
});
|
||||
|
||||
document.getElementById('signin-form')?.addEventListener('submit', (e) => {
|
||||
const sig = (document.getElementById('gpg_signature') as HTMLTextAreaElement).value.trim();
|
||||
document.querySelector('signin-form')?.addEventListener('submit', (e) => {
|
||||
const sig = (document.querySelector('gpg_signature') as HTMLTextAreaElement).value.trim();
|
||||
if (!validateSignature(sig)) {
|
||||
e.preventDefault();
|
||||
alert('Paste the GPG signed output.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,49 @@
|
||||
import { generateNonce, buildSignCommand, validateSignature } from './gpg-nonce.ts';
|
||||
import {makeNonce, buildSignCommand, validateSignature} from './gpg-nonce.ts';
|
||||
|
||||
export function initGpgSignup() {
|
||||
const btnProceed = document.querySelector('#btn-proceed');
|
||||
const btnProceed = document.querySelector('btn-proceed');
|
||||
if (!btnProceed) return;
|
||||
|
||||
const nonce = generateNonce();
|
||||
const nonce = makeNonce();
|
||||
|
||||
// if step-sign already visible (error re-render), populate now
|
||||
const stepSign = document.querySelector('step-sign');
|
||||
if (stepSign && (stepSign as HTMLElement).style.display !== 'none') {
|
||||
(document.querySelector('token-field') as HTMLInputElement).value = nonce;
|
||||
(document.querySelector('hidden-nonce') as HTMLInputElement).value = nonce;
|
||||
(document.querySelector('sign-command') as HTMLInputElement).value = buildSignCommand(nonce);
|
||||
}
|
||||
|
||||
btnProceed.addEventListener('click', () => {
|
||||
const key = (document.querySelector('#gpg_key') as HTMLTextAreaElement).value.trim();
|
||||
const key = (document.querySelector('gpg_key') as HTMLTextAreaElement).value
|
||||
.trim()
|
||||
.replace(/\r\n/g, '\n')
|
||||
.replace(/\r/g, '\n');
|
||||
|
||||
if (!key || !key.startsWith('-----BEGIN PGP PUBLIC KEY BLOCK-----')) {
|
||||
alert('Paste a valid armored GPG public key.');
|
||||
return;
|
||||
}
|
||||
(document.querySelector('#token-field') as HTMLInputElement).value = nonce;
|
||||
(document.querySelector('#hidden-nonce') as HTMLInputElement).value = nonce;
|
||||
(document.querySelector('#hidden-gpg-key') as HTMLInputElement).value = key;
|
||||
(document.querySelector('#sign-command') as HTMLInputElement).value =
|
||||
buildSignCommand(nonce);
|
||||
(document.querySelector('#step-key') as HTMLElement).style.display = 'none';
|
||||
(document.querySelector('#step-sign') as HTMLElement).style.display = 'block';
|
||||
|
||||
(document.querySelector('token-field') as HTMLInputElement).value = nonce;
|
||||
(document.querySelector('hidden-nonce') as HTMLInputElement).value = nonce;
|
||||
(document.querySelector('hidden-gpg-key') as HTMLInputElement).value = key;
|
||||
(document.querySelector('sign-command') as HTMLInputElement).value = buildSignCommand(nonce);
|
||||
(document.querySelector('step-key') as HTMLElement).style.display = 'none';
|
||||
(document.querySelector('step-sign') as HTMLElement).style.display = 'block';
|
||||
});
|
||||
|
||||
document.querySelector('#signup-form')?.addEventListener('submit', (e) => {
|
||||
const sig = (document.querySelector('#gpg_signature') as HTMLTextAreaElement).value.trim();
|
||||
if (!validateSignature(sig)) {
|
||||
e.preventDefault();
|
||||
alert('Paste the GPG signed output.');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#btn-copy-cmd')?.addEventListener('click', () => {
|
||||
document.querySelector('btn-copy-cmd')?.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(
|
||||
(document.querySelector('#sign-command') as HTMLInputElement).value,
|
||||
(document.querySelector('sign-command') as HTMLInputElement).value,
|
||||
);
|
||||
});
|
||||
|
||||
document.querySelector('#signup-form')?.addEventListener('submit', (e) => {
|
||||
const sig = (document.querySelector('#gpg_signature') as HTMLTextAreaElement).value.trim();
|
||||
document.querySelector('signup-form')?.addEventListener('submit', (e) => {
|
||||
const sig = (document.querySelector('gpg_signature') as HTMLTextAreaElement).value.trim();
|
||||
if (!validateSignature(sig)) {
|
||||
e.preventDefault();
|
||||
alert('Paste the GPG signed output.');
|
||||
return;
|
||||
}
|
||||
sessionStorage.removeItem('gpg_signup_nonce');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,27 +1,42 @@
|
||||
import { initGpgNonceWidget } from './gpg-nonce.ts';
|
||||
import {makeNonce, buildSignCommand} from './gpg-nonce.ts';
|
||||
|
||||
export function initGpgKeySettings() {
|
||||
// add key flow — only active when Err_Signature is present
|
||||
initGpgNonceWidget({
|
||||
tokenFieldId: 'add-key-token-field',
|
||||
nonceInputId: 'add-key-hidden-nonce',
|
||||
signCommandId: 'add-key-sign-command',
|
||||
copyBtnId: 'add-key-btn-copy-cmd',
|
||||
signatureId: 'gpg-key-signature',
|
||||
formId: 'add-key-form',
|
||||
const addTokenField = document.querySelector('add-key-token-field') as HTMLInputElement;
|
||||
if (addTokenField) {
|
||||
const nonce = makeNonce();
|
||||
addTokenField.value = nonce;
|
||||
(document.querySelector('add-key-hidden-nonce') as HTMLInputElement).value = nonce;
|
||||
(document.querySelector('add-key-sign-command') as HTMLInputElement).value = buildSignCommand(nonce);
|
||||
document.querySelector('add-key-btn-copy-cmd')?.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(
|
||||
(document.querySelector('add-key-sign-command') as HTMLInputElement).value,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const verifyTokenField = document.querySelector('verify-key-token-field') as HTMLInputElement;
|
||||
if (verifyTokenField) {
|
||||
const nonce = makeNonce();
|
||||
const keyID = (document.querySelector('verify-key-form') as HTMLElement)?.getAttribute('keyId') ?? '';
|
||||
verifyTokenField.value = nonce;
|
||||
(document.querySelector('verify-key-hidden-nonce') as HTMLInputElement).value = nonce;
|
||||
(document.querySelector('verify-key-sign-command') as HTMLInputElement).value = buildSignCommand(nonce, keyID);
|
||||
document.querySelector('verify-key-btn-copy-cmd')?.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(
|
||||
(document.querySelector('verify-key-sign-command') as HTMLInputElement).value,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelector('add-key-btn-copy-cmd')?.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(
|
||||
(document.querySelector('add-key-sign-command') as HTMLInputElement).value,
|
||||
);
|
||||
});
|
||||
|
||||
// verify flow — keyID known from data attribute
|
||||
const verifyForm = document.querySelector('#verify-key-form');
|
||||
const keyID = verifyForm?.getAttribute('keyId') ?? '';
|
||||
|
||||
initGpgNonceWidget({
|
||||
tokenFieldId: 'verify-key-token-field',
|
||||
nonceInputId: 'verify-key-hidden-nonce',
|
||||
signCommandId: 'verify-key-sign-command',
|
||||
copyBtnId: 'verify-key-btn-copy-cmd',
|
||||
signatureId: 'gpg-key-signature',
|
||||
formId: 'verify-key-form',
|
||||
keyID,
|
||||
document.querySelector('verify-key-btn-copy-cmd')?.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(
|
||||
(document.querySelector('verify-key-sign-command') as HTMLInputElement).value,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user