Implements deleteUser and updateProfile in backend.
This commit is contained in:
parent
dc72aeb677
commit
2ab20bf7a5
3 changed files with 117 additions and 19 deletions
107
src/forum.nim
107
src/forum.nim
|
|
@ -1144,6 +1144,19 @@ proc executeLogin(c: TForumData, username, password: string): string =
|
||||||
|
|
||||||
raise newForumError("Invalid username or password")
|
raise newForumError("Invalid username or password")
|
||||||
|
|
||||||
|
proc sendEmailActivation(c: TForumData, name, password,
|
||||||
|
email, salt: string) {.async.} =
|
||||||
|
let epoch = $int(epochTime())
|
||||||
|
let activateUrl = c.req.makeUri("/activateEmail?nick=$1&epoch=$2&ident=$3" %
|
||||||
|
[encodeUrl(name), encodeUrl(epoch),
|
||||||
|
encodeUrl(makeIdentHash(name, password, epoch, salt))])
|
||||||
|
|
||||||
|
let emailSentFut = sendEmailActivation(c.config, email, name, activateUrl)
|
||||||
|
yield emailSentFut
|
||||||
|
if emailSentFut.failed:
|
||||||
|
echo("[WARNING] Couldn't send activation email: ", emailSentFut.error.msg)
|
||||||
|
raise newForumError("Couldn't send activation email", @["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
|
||||||
|
|
@ -1158,7 +1171,7 @@ proc executeRegister(c: TForumData, name, pass, antibot, userIp,
|
||||||
raise newForumError("Email already exists", @["email"])
|
raise newForumError("Email already exists", @["email"])
|
||||||
|
|
||||||
# Username validation:
|
# Username validation:
|
||||||
if name.len == 0 or not allCharsInSet(name, UsernameIdent):
|
if name.len == 0 or not allCharsInSet(name, UsernameIdent) or name.len > 20:
|
||||||
raise newForumError("Invalid username", @["username"])
|
raise newForumError("Invalid username", @["username"])
|
||||||
if getValue(db, sql"select name from person where name = ?", name).len > 0:
|
if getValue(db, sql"select name from person where name = ?", name).len > 0:
|
||||||
raise newForumError("Username already exists", @["username"])
|
raise newForumError("Username already exists", @["username"])
|
||||||
|
|
@ -1181,16 +1194,7 @@ proc executeRegister(c: TForumData, name, pass, antibot, userIp,
|
||||||
let password = makePassword(pass, salt)
|
let password = makePassword(pass, salt)
|
||||||
|
|
||||||
# Send activation email.
|
# Send activation email.
|
||||||
let epoch = $int(epochTime())
|
await sendEmailActivation(c, name, password, email, salt)
|
||||||
let activateUrl = c.req.makeUri("/activateEmail?nick=$1&epoch=$2&ident=$3" %
|
|
||||||
[encodeUrl(name), encodeUrl(epoch),
|
|
||||||
encodeUrl(makeIdentHash(name, password, epoch, salt))])
|
|
||||||
|
|
||||||
let emailSentFut = sendEmailActivation(c.config, email, name, activateUrl)
|
|
||||||
yield emailSentFut
|
|
||||||
if emailSentFut.failed:
|
|
||||||
echo("[WARNING] Couldn't send activation email: ", emailSentFut.error.msg)
|
|
||||||
raise newForumError("Couldn't send activation email", @["email"])
|
|
||||||
|
|
||||||
# Add account to person table
|
# Add account to person table
|
||||||
exec(db, sql"""
|
exec(db, sql"""
|
||||||
|
|
@ -1254,6 +1258,42 @@ proc executeDeleteThread(c: TForumData, threadId: int) =
|
||||||
# Set the `isDeleted` flag.
|
# Set the `isDeleted` flag.
|
||||||
exec(db, crud(crUpdate, "thread", "isDeleted"), "1", threadId)
|
exec(db, crud(crUpdate, "thread", "isDeleted"), "1", threadId)
|
||||||
|
|
||||||
|
proc executeDeleteUser(c: TForumData, username: string) =
|
||||||
|
# Verify that the current user has the permissions to do this.
|
||||||
|
if username != c.username and c.rank < Admin:
|
||||||
|
raise newForumError("You cannot delete this user.")
|
||||||
|
|
||||||
|
# Set the `isDeleted` flag.
|
||||||
|
exec(db, sql"update person set isDeleted = 1 where name = ?;", username)
|
||||||
|
|
||||||
|
proc updateProfile(
|
||||||
|
c: TForumData, username, email: string, rank: Rank
|
||||||
|
) {.async.} =
|
||||||
|
if c.rank < rank:
|
||||||
|
raise newForumError("You cannot set a rank that is higher than yours.")
|
||||||
|
|
||||||
|
if c.username != username and c.rank < Moderator:
|
||||||
|
raise newForumError("You can't change this profile.")
|
||||||
|
|
||||||
|
# Make sure the rank is set to EmailUnconfirmed when the email changes.
|
||||||
|
if c.rank < Moderator:
|
||||||
|
let row = getRow(
|
||||||
|
db,
|
||||||
|
sql"select name, password, email, salt from person where name = ?",
|
||||||
|
username
|
||||||
|
)
|
||||||
|
if row[2] != email:
|
||||||
|
if rank != EmailUnconfirmed:
|
||||||
|
raise newForumError("Rank needs a change when setting new email.")
|
||||||
|
|
||||||
|
await sendEmailActivation(c, row[0], row[1], row[2], row[3])
|
||||||
|
|
||||||
|
exec(
|
||||||
|
db,
|
||||||
|
sql"update person set status = ?, email = ? where name = ?;",
|
||||||
|
$rank, email, username
|
||||||
|
)
|
||||||
|
|
||||||
initialise()
|
initialise()
|
||||||
|
|
||||||
routes:
|
routes:
|
||||||
|
|
@ -1706,6 +1746,51 @@ routes:
|
||||||
except ForumError as exc:
|
except ForumError as exc:
|
||||||
resp Http400, $(%exc.data), "application/json"
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
|
post re"/deleteUser":
|
||||||
|
createTFD()
|
||||||
|
if not c.loggedIn():
|
||||||
|
let err = PostError(
|
||||||
|
errorFields: @[],
|
||||||
|
message: "Not logged in."
|
||||||
|
)
|
||||||
|
resp Http401, $(%err), "application/json"
|
||||||
|
|
||||||
|
let formData = request.formData
|
||||||
|
cond "username" in formData
|
||||||
|
|
||||||
|
let username = formData["username"].body
|
||||||
|
|
||||||
|
try:
|
||||||
|
executeDeleteUser(c, username)
|
||||||
|
resp Http200, "{}", "application/json"
|
||||||
|
except ForumError as exc:
|
||||||
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
|
post re"/saveProfile":
|
||||||
|
createTFD()
|
||||||
|
if not c.loggedIn():
|
||||||
|
let err = PostError(
|
||||||
|
errorFields: @[],
|
||||||
|
message: "Not logged in."
|
||||||
|
)
|
||||||
|
resp Http401, $(%err), "application/json"
|
||||||
|
|
||||||
|
let formData = request.formData
|
||||||
|
cond "username" in formData
|
||||||
|
cond "email" in formData
|
||||||
|
cond "rank" in formData
|
||||||
|
|
||||||
|
let username = formData["username"].body
|
||||||
|
let email = formData["email"].body
|
||||||
|
let rank = parseEnum[Rank](formData["rank"].body)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await updateProfile(c, username, email, rank)
|
||||||
|
resp Http200, "{}", "application/json"
|
||||||
|
except ForumError:
|
||||||
|
let exc = (ref ForumError)(getCurrentException())
|
||||||
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
get "/t/@id":
|
get "/t/@id":
|
||||||
cond "id" in request.params
|
cond "id" in request.params
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@ when defined(js):
|
||||||
let profile = to(parsed, Profile)
|
let profile = to(parsed, Profile)
|
||||||
|
|
||||||
state.profile = some(profile)
|
state.profile = some(profile)
|
||||||
state.settings = some(newProfileSettings(profile))
|
if profile.email.isSome():
|
||||||
|
state.settings = some(newProfileSettings(profile))
|
||||||
|
|
||||||
proc genPostLink(link: PostLink): VNode =
|
proc genPostLink(link: PostLink): VNode =
|
||||||
let url = renderPostUrl(link)
|
let url = renderPostUrl(link)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ when defined(js):
|
||||||
state.email = profile.email.get()
|
state.email = profile.email.get()
|
||||||
state.rank = profile.user.rank
|
state.rank = profile.user.rank
|
||||||
|
|
||||||
|
state.error = none[PostError]()
|
||||||
|
|
||||||
proc newProfileSettings*(profile: Profile): ProfileSettings =
|
proc newProfileSettings*(profile: Profile): ProfileSettings =
|
||||||
result = ProfileSettings(
|
result = ProfileSettings(
|
||||||
status: Http200,
|
status: Http200,
|
||||||
|
|
@ -38,7 +40,8 @@ when defined(js):
|
||||||
proc onProfilePost(httpStatus: int, response: kstring,
|
proc onProfilePost(httpStatus: int, response: kstring,
|
||||||
state: ProfileSettings) =
|
state: ProfileSettings) =
|
||||||
postFinished:
|
postFinished:
|
||||||
discard
|
state.profile.email = some($state.email)
|
||||||
|
state.profile.user.rank = state.rank
|
||||||
|
|
||||||
proc onEmailChange(event: Event, node: VNode, state: ProfileSettings) =
|
proc onEmailChange(event: Event, node: VNode, state: ProfileSettings) =
|
||||||
state.email = node.value
|
state.email = node.value
|
||||||
|
|
@ -66,11 +69,12 @@ when defined(js):
|
||||||
ajaxPost(uri, @[], cast[cstring](formData),
|
ajaxPost(uri, @[], cast[cstring](formData),
|
||||||
(s: int, r: kstring) => onProfilePost(s, r, state))
|
(s: int, r: kstring) => onProfilePost(s, r, state))
|
||||||
|
|
||||||
|
proc needsSave(state: ProfileSettings): bool =
|
||||||
|
state.email != state.profile.email.get() or
|
||||||
|
state.rank != state.profile.user.rank
|
||||||
|
|
||||||
proc render*(state: ProfileSettings,
|
proc render*(state: ProfileSettings,
|
||||||
currentUser: Option[User]): VNode =
|
currentUser: Option[User]): VNode =
|
||||||
if state.status != Http200:
|
|
||||||
return renderError("Couldn't save profile")
|
|
||||||
|
|
||||||
let isAdmin = currentUser.isSome() and currentUser.get().rank == Admin
|
let isAdmin = currentUser.isSome() and currentUser.get().rank == Admin
|
||||||
let canResetPassword = state.profile.user.rank > EmailUnconfirmed
|
let canResetPassword = state.profile.user.rank > EmailUnconfirmed
|
||||||
|
|
||||||
|
|
@ -163,13 +167,21 @@ when defined(js):
|
||||||
text " Delete account"
|
text " Delete account"
|
||||||
|
|
||||||
tdiv(class="float-right"):
|
tdiv(class="float-right"):
|
||||||
button(class="btn btn-link",
|
if state.error.isSome():
|
||||||
|
span(class="text-error"):
|
||||||
|
text state.error.get().message
|
||||||
|
|
||||||
|
button(class=class(
|
||||||
|
{"disabled": not needsSave(state)}, "btn btn-link"
|
||||||
|
),
|
||||||
onClick=(e: Event, n: VNode) => (resetSettings(state))):
|
onClick=(e: Event, n: VNode) => (resetSettings(state))):
|
||||||
text "Cancel"
|
text "Cancel"
|
||||||
|
|
||||||
button(class="btn btn-primary",
|
button(class=class(
|
||||||
|
{"disabled": not needsSave(state)}, "btn btn-primary"
|
||||||
|
),
|
||||||
onClick=(e: Event, n: VNode) => save(state)):
|
onClick=(e: Event, n: VNode) => save(state)):
|
||||||
italic(class="fas fa-check")
|
italic(class="fas fa-save")
|
||||||
text " Save"
|
text " Save"
|
||||||
|
|
||||||
render(state.deleteModal)
|
render(state.deleteModal)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue