Allow moderators to edit profiles, but don't show them the emails.

This commit is contained in:
Dominik Picheta 2018-05-20 18:25:51 +01:00
commit f8a9909278
4 changed files with 70 additions and 48 deletions

View file

@ -126,8 +126,11 @@ proc resetPassword(
) {.async.} = ) {.async.} =
# Gather some extra information to determine ident hash. # Gather some extra information to determine ident hash.
let row = db.getRow( let row = db.getRow(
sql"select name, password, email, salt from person where email = ?", sql"""
email select name, password, email, salt from person
where email = ? or name = ?
""",
email, email
) )
if row[0] == "": if row[0] == "":
raise newForumError("Email not found", @["email"]) raise newForumError("Email not found", @["email"])
@ -559,18 +562,22 @@ proc executeLogin(c: TForumData, username, password: string): string =
raise newForumError("Invalid username or password") raise newForumError("Invalid username or password")
proc validateEmail(email: string, checkDuplicated: bool) =
if not ('@' in email and '.' in email):
raise newForumError("Invalid email", @["email"])
if checkDuplicated:
if getValue(
db, sql"select email from person where email = ?", email
).len > 0:
raise newForumError("Email already exists", @["email"])
proc executeRegister(c: TForumData, name, pass, antibot, userIp, proc executeRegister(c: TForumData, name, pass, antibot, userIp,
email: string): Future[string] {.async.} = email: string): Future[string] {.async.} =
## Registers a new user and returns a new session key for that user's ## Registers a new user and returns a new session key for that user's
## session if registration was successful. Exceptions are raised otherwise. ## session if registration was successful. Exceptions are raised otherwise.
# email validation # email validation
if not ('@' in email and '.' in email): validateEmail(email, checkDuplicated=true)
raise newForumError("Invalid email", @["email"])
if getValue(
db, sql"select email from person where email = ?", email
).len > 0:
raise newForumError("Email already exists", @["email"])
# Username validation: # Username validation:
if name.len == 0 or not allCharsInSet(name, UsernameIdent) or name.len > 20: if name.len == 0 or not allCharsInSet(name, UsernameIdent) or name.len > 20:
@ -681,20 +688,31 @@ proc updateProfile(
if c.username != username and c.rank < Moderator: if c.username != username and c.rank < Moderator:
raise newForumError("You can't change this profile.") raise newForumError("You can't change this profile.")
# Make sure the rank is set to EmailUnconfirmed when the email changes. # Check if we are only setting the rank.
if c.rank < Moderator: if email.len == 0:
let row = getRow( exec(
db, db,
sql"select name, password, email, salt from person where name = ?", sql"update person set status = ? where name = ?;",
username $rank, username
) )
if row[2] != email: return
if rank != EmailUnconfirmed:
raise newForumError("Rank needs a change when setting new email.")
await sendSecureEmail( # Make sure the rank is set to EmailUnconfirmed when the email changes.
mailer, ActivateEmail, c.req, row[0], row[1], row[2], row[3] let row = getRow(
) db,
sql"select name, password, email, salt from person where name = ?",
username
)
let wasEmailChanged = row[2] != email
if c.rank < Moderator and wasEmailChanged:
if rank != EmailUnconfirmed:
raise newForumError("Rank needs a change when setting new email.")
await sendSecureEmail(
mailer, ActivateEmail, c.req, row[0], row[1], row[2], row[3]
)
validateEmail(email, checkDuplicated=wasEmailChanged)
exec( exec(
db, db,

View file

@ -27,9 +27,9 @@ when defined(js):
icon: icon icon: icon
) )
proc newResetPasswordButton*(email: string): PostButton = proc newResetPasswordButton*(username: string): PostButton =
var formData = newFormData() var formData = newFormData()
formData.append("email", email) formData.append("email", username)
result = newPostButton( result = newPostButton(
makeUri("/sendResetPassword"), makeUri("/sendResetPassword"),
formData, formData,

View file

@ -35,8 +35,7 @@ when defined(js):
let profile = to(parsed, Profile) let profile = to(parsed, Profile)
state.profile = some(profile) state.profile = some(profile)
if profile.email.isSome(): state.settings = some(newProfileSettings(profile))
state.settings = some(newProfileSettings(profile))
proc genPostLink(link: PostLink): VNode = proc genPostLink(link: PostLink): VNode =
let url = renderPostUrl(link) let url = renderPostUrl(link)
@ -106,7 +105,7 @@ when defined(js):
if currentUser.isSome(): if currentUser.isSome():
let user = currentUser.get() let user = currentUser.get()
if user.name == profile.user.name or user.rank == Admin: if user.name == profile.user.name or user.rank >= Moderator:
ul(class="tab"): ul(class="tab"):
li(class=class( li(class=class(
{"active": state.currentTab == Overview}, {"active": state.currentTab == Overview},

View file

@ -24,7 +24,9 @@ when defined(js):
let profile = state.profile let profile = state.profile
if profile.email.isSome(): if profile.email.isSome():
state.email = profile.email.get() state.email = profile.email.get()
state.rank = profile.user.rank else:
state.email = ""
state.rank = profile.user.rank
state.error = none[PostError]() state.error = none[PostError]()
@ -32,7 +34,7 @@ when defined(js):
result = ProfileSettings( result = ProfileSettings(
status: Http200, status: Http200,
deleteModal: newDeleteModal(nil, nil, onUserDelete), deleteModal: newDeleteModal(nil, nil, onUserDelete),
resetPassword: newResetPasswordButton(profile.email.get()), resetPassword: newResetPasswordButton(profile.user.name),
profile: profile profile: profile
) )
resetSettings(result) resetSettings(result)
@ -70,23 +72,25 @@ when defined(js):
(s: int, r: kstring) => onProfilePost(s, r, state)) (s: int, r: kstring) => onProfilePost(s, r, state))
proc needsSave(state: ProfileSettings): bool = proc needsSave(state: ProfileSettings): bool =
state.email != state.profile.email.get() or if state.profile.email.isSome():
state.rank != state.profile.user.rank result = state.email != state.profile.email.get()
result = result or state.rank != state.profile.user.rank
proc render*(state: ProfileSettings, proc render*(state: ProfileSettings,
currentUser: Option[User]): VNode = currentUser: Option[User]): VNode =
let isAdmin = currentUser.isSome() and currentUser.get().rank == Admin let canEditRank = currentUser.isSome() and
currentUser.get().rank > state.profile.user.rank
let canResetPassword = state.profile.user.rank > EmailUnconfirmed let canResetPassword = state.profile.user.rank > EmailUnconfirmed
let rankSelect = buildHtml(tdiv()): let rankSelect = buildHtml(tdiv()):
if isAdmin: if canEditRank:
select(id="rank-field", select(id="rank-field",
class="form-select", value = $state.rank, class="form-select", value = $state.rank,
onchange=(e: Event, n: VNode) => onRankChange(e, n, state)): onchange=(e: Event, n: VNode) => onRankChange(e, n, state)):
for r in Rank: for r in Rank:
option(text $r) option(text $r)
p(class="form-input-hint text-warning"): p(class="form-input-hint text-warning"):
text "As an admin you can modify anyone's rank. Remember: with " & text "You can modify anyone's rank. Remember: with " &
"great power comes great responsibility." "great power comes great responsibility."
else: else:
input(id="rank-field", class="form-input", input(id="rank-field", class="form-input",
@ -125,23 +129,24 @@ when defined(js):
p(class="form-input-hint"): p(class="form-input-hint"):
text fmt("Users can refer to you by writing" & text fmt("Users can refer to you by writing" &
" @{state.profile.user.name} in their posts.") " @{state.profile.user.name} in their posts.")
tdiv(class="form-group"): if state.profile.email.isSome():
tdiv(class="col-3 col-sm-12"): tdiv(class="form-group"):
label(class="form-label"): tdiv(class="col-3 col-sm-12"):
text "Email" label(class="form-label"):
tdiv(class="col-9 col-sm-12"): text "Email"
input(id="email-input", class="form-input", tdiv(class="col-9 col-sm-12"):
`type`="text", value=state.email, input(id="email-input", class="form-input",
oninput=(e: Event, n: VNode) => `type`="text", value=state.email,
onEmailChange(e, n, state) oninput=(e: Event, n: VNode) =>
) onEmailChange(e, n, state)
p(class="form-input-hint"): )
text "Your avatar is linked to this email and can be " & p(class="form-input-hint"):
"changed at " text "Your avatar is linked to this email and can be " &
a(href="https://gravatar.com/emails"): "changed at "
text "gravatar.com" a(href="https://gravatar.com/emails"):
text ". Note that any changes to your email will " & text "gravatar.com"
"require email verification." text ". Note that any changes to your email will " &
"require email verification."
tdiv(class="form-group"): tdiv(class="form-group"):
tdiv(class="col-3 col-sm-12"): tdiv(class="col-3 col-sm-12"):
label(class="form-label"): label(class="form-label"):