登陆页面
新建views/signin.ejs,比较简单,就是个包含了用户名密码的表单。
<%- include('header') %>
<div class="ui grid">
<div class="four wide column"></div>
<div class="eight wide column">
<form class="ui form segment" method="post" enctype="multipart/form-data" action="/signin">
<div class="field required">
<label>用户名</label>
<input placeholder="用户名" type="text" name="name" minlength="1" maxlength="10" required>
</div>
<div class="field required">
<label>密码</label>
<input placeholder="密码" type="password" name="password" minlength="6" maxlength="20" required>
</div>
<input type="submit" class="ui button fluid" value="登录">
</form>
</div>
</div>
<%- include('footer') %>
在src/user/user.controller.ts中增加signin的GET接口:
@Get("signin")
signinPage(@Render() render: Render) {
return render("signin", {});
}
这时访问http://localhost:8000/signin,已经能看到页面了:
登陆接口
修改src/user/user.dto.ts,新增一个SigninDto:
export class SigninDto {
@IsString()
@MinLength(1)
@MaxLength(10)
name: string;
@IsString()
@MinLength(6)
@MaxLength(20)
password: string;
}
在src/user/user.service.ts新增一个方法:
findByName(name: string) {
return this.userModel.findOne({ name });
}
在src/user/user.controller.ts中增加signin的POST接口:
/** 登陆 */
@Post("signin")
async signin(
@FormData() params: FormDataFormattedBody<SigninDto>,
@Res() res: Response,
@Flash() flash: Flash,
) {
const fields = params.fields;
try {
await validateParams(SigninDto, fields);
} catch (error) {
flash("error", error.message);
return res.redirect(REDIRECT_BACK);
}
const username = fields.name;
const user = await this.userService.findByName(username);
let error = "";
if (!user) {
this.logger.error(`用户名${username}不存在`);
error = "用户名或密码错误";
} else if (fields.password !== user.password) {
this.logger.error(`密码${fields.password}不匹配`);
error = "用户名或密码错误";
}
if (error) {
flash("error", error);
return res.redirect(REDIRECT_BACK);
}
assert(user);
flash("userId", user.id!);
flash("success", "登录成功");
this.logger.info(`用户${username}登陆成功`);
// 跳转到主页
res.redirect("posts");
}
FormData这个装饰器也引自oak_nest。
登出接口
前文说过,登出接口及删除操作不推荐使用GET,而应该用DELETE或者POST,为什么呢?因为GET请求更易于CSRF攻击,虽然后者也没法避免。
@Get("signout")
async signout(@Res() res: Response, @Flash() flash: Flash) {
flash("userId", "");
flash("success", "登出成功");
res.redirect('posts');
}
验证
- 用正确的信息登陆
- 用错误的信息登陆:
- 在登陆成功后,退出登陆
- 反复切换登陆登出,查看登陆状态是否正常
作业
试想一下,假设用户已经登陆,但仍访问注册页面,是否不合适?
又或者用户没有登陆,但要访问必须登陆者才能访问的页面,怎么办?