mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-02-22 22:06:21 -05:00
Add a new option that allows instances to set a cooldown period to claim old usernames. In the context of public instances this can be used to prevent old usernames to be claimed after they are free and allow graceful migration (by making use of the redirect feature) to a new username. The granularity of this cooldown is a day. By default this feature is disabled and thus no cooldown period. The `CreatedUnix` column is added the `user_redirect` table, for existing redirects the timestamp is simply zero as we simply do not know when they were created and are likely already over the cooldown period if the instance configures one. Users can always reclaim their 'old' user name again within the cooldown period. Users can also always reclaim 'old' names of organization they currently own within the cooldown period. Creating and renaming users as an admin user are not affected by the cooldown period for moderation and user support reasons. To avoid abuse of the cooldown feature, such that a user holds a lot of usernames, a new option is added `MAX_USER_REDIRECTS` which sets a limit to the amount of user redirects a user may have, by default this is disabled. If a cooldown period is set then the default is 5. This feature operates independently of the cooldown period feature. Added integration and unit testing. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6422 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Reviewed-by: 0ko <0ko@noreply.codeberg.org> Reviewed-by: Otto <otto@codeberg.org> Co-authored-by: Gusted <postmaster@gusted.xyz> Co-committed-by: Gusted <postmaster@gusted.xyz>
167 lines
8.3 KiB
Go HTML Template
167 lines
8.3 KiB
Go HTML Template
{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings profile")}}
|
|
<div class="user-setting-content">
|
|
<h4 class="ui top attached header">
|
|
{{ctx.Locale.Tr "settings.public_profile"}}
|
|
</h4>
|
|
<div class="ui attached segment">
|
|
<form class="ui form" action="{{.Link}}" method="post">
|
|
{{.CsrfTokenHtml}}
|
|
<fieldset>
|
|
<legend>{{ctx.Locale.Tr "settings.profile_desc"}}</legend>
|
|
<label {{if .Err_Name}}class="field error"{{end}}>
|
|
{{ctx.Locale.Tr "username"}}
|
|
<input name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) .IsReverseProxy}}disabled{{end}} maxlength="40">
|
|
{{if or (not .SignedUser.IsLocal) .IsReverseProxy}}
|
|
<span class="help">{{ctx.Locale.Tr "settings.password_username_disabled"}}</span>
|
|
{{else}}
|
|
<span class="help">
|
|
{{ctx.Locale.Tr "settings.change_username_prompt"}}
|
|
{{if gt .CooldownPeriod 0}}
|
|
{{ctx.Locale.TrN .CooldownPeriod "settings.change_username_redirect_prompt.with_cooldown.one" "settings.change_username_redirect_prompt.with_cooldown.few" .CooldownPeriod}}</span>
|
|
{{else}}
|
|
{{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}
|
|
{{end}}
|
|
</span>
|
|
{{end}}
|
|
</label>
|
|
|
|
<label {{if .Err_FullName}}class="field error"{{end}}>
|
|
{{ctx.Locale.Tr "settings.full_name"}}
|
|
<input name="full_name" value="{{.SignedUser.FullName}}" maxlength="100">
|
|
</label>
|
|
|
|
<label id="label-pronouns" class="tw-hidden">
|
|
{{ctx.Locale.Tr "settings.pronouns"}}
|
|
<div id="pronouns-dropdown" class="ui selection dropdown" aria-labelledby="label-pronouns">
|
|
<input type="hidden" value="{{.SignedUser.Pronouns}}">
|
|
<div class="text">
|
|
{{if .PronounsAreCustom}}
|
|
{{ctx.Locale.Tr "settings.pronouns_custom"}}
|
|
{{else if eq "" .SignedUser.Pronouns}}
|
|
{{ctx.Locale.Tr "settings.pronouns_unspecified"}}
|
|
{{else}}
|
|
{{.SignedUser.Pronouns}}
|
|
{{end}}
|
|
</div>
|
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
<div class="menu">
|
|
<div class="item{{if eq "" .SignedUser.Pronouns}} active selected{{end}}" data-value=""><p>{{ctx.Locale.Tr "settings.pronouns_unspecified"}}</p></div>
|
|
<div class="item{{if eq "he/him" .SignedUser.Pronouns}} active selected{{end}}" data-value="he/him">he/him</div>
|
|
<div class="item{{if eq "she/her" .SignedUser.Pronouns}} active selected{{end}}" data-value="she/her">she/her</div>
|
|
<div class="item{{if eq "they/them" .SignedUser.Pronouns}} active selected{{end}}" data-value="they/them">they/them</div>
|
|
<div class="item{{if eq "it/its" .SignedUser.Pronouns}} active selected{{end}}" data-value="it/its">it/its</div>
|
|
<div class="item{{if eq "any pronouns" .SignedUser.Pronouns}} active selected{{end}}" data-value="any pronouns">any pronouns</div>
|
|
{{if .PronounsAreCustom}}
|
|
<div class="item active selected" data-value="{{.SignedUser.Pronouns}}">{{ctx.Locale.Tr "settings.pronouns_custom"}}</div>
|
|
{{else}}
|
|
<div class="item" data-value="!"><i>{{ctx.Locale.Tr "settings.pronouns_custom"}}</i></div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</label>
|
|
<label id="label-pronouns-custom">
|
|
{{ctx.Locale.Tr "settings.pronouns_custom_label"}}
|
|
<input name="pronouns" value="{{.SignedUser.Pronouns}}" maxlength="50">
|
|
</label>
|
|
|
|
<label {{if .Err_Biography}}class="field error"{{end}}>
|
|
{{ctx.Locale.Tr "user.user_bio"}}
|
|
<textarea name="biography" rows="2" placeholder="{{ctx.Locale.Tr "settings.biography_placeholder"}}" maxlength="255">{{.SignedUser.Description}}</textarea>
|
|
</label>
|
|
|
|
<label {{if .Err_Website}}class="field error"{{end}}>
|
|
{{ctx.Locale.Tr "settings.website"}}
|
|
<input name="website" type="url" value="{{.SignedUser.Website}}" maxlength="255">
|
|
</label>
|
|
|
|
<label>
|
|
{{ctx.Locale.Tr "settings.location"}}
|
|
<input name="location" placeholder="{{ctx.Locale.Tr "settings.location_placeholder"}}" value="{{.SignedUser.Location}}" maxlength="50">
|
|
</label>
|
|
</fieldset>
|
|
|
|
<fieldset>
|
|
<legend id="privacy-user-settings">{{ctx.Locale.Tr "settings.privacy"}}</legend>
|
|
|
|
<label id="visibility-setting" {{if .Err_Visibility}}class="field error"{{end}}>
|
|
{{ctx.Locale.Tr "settings.visibility"}}
|
|
<div class="ui selection type dropdown" aria-labelledby="visibility-setting">
|
|
{{if .SignedUser.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}}
|
|
{{if .SignedUser.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}}
|
|
{{if .SignedUser.Visibility.IsPrivate}}<input type="hidden" id="visibility" name="visibility" value="2">{{end}}
|
|
<div class="text">
|
|
{{if .SignedUser.Visibility.IsPublic}}{{ctx.Locale.Tr "settings.visibility.public"}}{{end}}
|
|
{{if .SignedUser.Visibility.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}
|
|
{{if .SignedUser.Visibility.IsPrivate}}{{ctx.Locale.Tr "settings.visibility.private"}}{{end}}
|
|
</div>
|
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
|
<div class="menu">
|
|
{{range $mode := .AllowedUserVisibilityModes}}
|
|
{{if $mode.IsPublic}}
|
|
<div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{ctx.Locale.Tr "settings.visibility.public"}}</div>
|
|
{{else if $mode.IsLimited}}
|
|
<div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{ctx.Locale.Tr "settings.visibility.limited"}}</div>
|
|
{{else if $mode.IsPrivate}}
|
|
<div class="item" data-tooltip-content="{{ctx.Locale.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{ctx.Locale.Tr "settings.visibility.private"}}</div>
|
|
{{end}}
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</label>
|
|
|
|
<label>
|
|
<input name="keep_email_private" type="checkbox" {{if .SignedUser.KeepEmailPrivate}}checked{{end}}>
|
|
{{ctx.Locale.Tr "settings.keep_email_private"}}
|
|
<span class="help">{{ctx.Locale.Tr "settings.keep_email_private_popup" .SignedUser.GetPlaceholderEmail}}</span>
|
|
</label>
|
|
|
|
<label id="keep-activity-private">
|
|
<input name="keep_activity_private" type="checkbox" {{if .SignedUser.KeepActivityPrivate}}checked{{end}}>
|
|
{{ctx.Locale.Tr "settings.keep_activity_private"}}
|
|
<span class="help">{{ctx.Locale.Tr "settings.keep_activity_private.description" (printf "/%s?tab=activity" .SignedUser.Name)}}</span>
|
|
</label>
|
|
</fieldset>
|
|
|
|
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_profile"}}</button>
|
|
</form>
|
|
</div>
|
|
|
|
<h4 class="ui top attached header">
|
|
{{ctx.Locale.Tr "settings.avatar"}}
|
|
</h4>
|
|
<div class="ui attached segment">
|
|
<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data">
|
|
{{.CsrfTokenHtml}}
|
|
{{if not .DisableGravatar}}
|
|
<div class="inline field">
|
|
<div class="ui radio checkbox">
|
|
<input name="source" value="lookup" type="radio" {{if not .SignedUser.UseCustomAvatar}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "settings.lookup_avatar_by_mail"}}</label>
|
|
</div>
|
|
</div>
|
|
<div class="field tw-pl-4 {{if .Err_Gravatar}}error{{end}}">
|
|
<label for="gravatar">Avatar {{ctx.Locale.Tr "email"}}</label>
|
|
<input id="gravatar" name="gravatar" value="{{.SignedUser.AvatarEmail}}">
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="inline field">
|
|
<div class="ui radio checkbox">
|
|
<input name="source" value="local" type="radio" {{if .SignedUser.UseCustomAvatar}}checked{{end}}>
|
|
<label>{{ctx.Locale.Tr "settings.enable_custom_avatar"}}</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="inline field tw-pl-4">
|
|
<label for="new-avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label>
|
|
<input id="new-avatar" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_avatar"}}</button>
|
|
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete">{{ctx.Locale.Tr "settings.delete_current_avatar"}}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{{template "user/settings/layout_footer" .}}
|