コンテンツ
Basolatoはミドルウェアで値が有効かどうかをチェックします。checkCsrfToken()とcheckSessionId()があります。
これらの関数は MiddlwareResult
オブジェクトを返します。
type MiddlewareResult* = ref object
isError: bool
message: string
proc isError*(self:MiddlewareResult):bool =
return self.isError
proc message*(self:MiddlewareResult):string =
return self.message
Basolatoは、リクエストメソッドが post
, put
, patch
, delete
の場合に、csrfトークンが有効かどうかをチェックすることができます。
main.nim
var routes = newRoutes()
routes.middleware(".*", auth_middleware.checkCsrfTokenMiddleware)
app/middlewares/auth_middleware.nim
proc checkCsrfTokenMiddleware*(r:Request, p:Params) {.async.} =
let res = await checkCsrfToken(r, p)
if res.isError:
raise newException(Error403, res.message)
ビューに${csrfToken()}
をセットします
<form method="POST">
$(csrfToken())
<input type="text" name="name">
<input type="text" name="password">
<button type="submit">login</button>
</form>
エラーが起きた時の処理を上書きすることができます。
# 403を返したい時
let res = await checkCsrfToken(r, p)
if res.isError:
raise newException(Error403, "Error message")
# ログインページにリダイレクトさせたい時
let res = await checkCsrfToken(r, p)
if res.isError:
raise newException(Error302, "/login")
セッションにはFile
かRedis
が使えます。
ファイルセッションはMongoに似たドキュメントDBのflatdbを使っています。
ファイルセッションの時
config.nims
putEnv("SESSION_TYPE", "file")
.env
SESSION_DB_PATH="/your/project/path/session.db" # db file path
SESSION_TIME=20160
Redisセッションの時
config.nims
putEnv("SESSION_TYPE", "redis")
.env
SESSION_DB_PATH="localhost:6379" # Redis IP address
SESSION_TIME=20160
Basolatoは認証とセッションを内包したClient
を持っています。
type Client* = ref object
session*: Session
インスタンス作成
proc newClient*(request:Request):Future[Client] {.async.} =
proc newClient*(sessionId:string):Future[Client] {.async.} =
セッションDBへのアクセス
proc set*(self:Client, key, value:string) {.async.} =
proc set*(self:Client, key:string, value:JsonNode) {.async.} =
proc some*(self:Client, key:string):Future[bool] {.async.} =
proc get*(self:Client, key:string):Future[string] {.async.} =
proc delete*(self:Client, key:string) {.async.} =
proc destroy*(self:Client) {.async.} =
認証
proc login*(self:Client) {.async.} =
proc isLogin*(self:Client):Future[bool] {.async.} =
proc logout*(self:Client) {.async.} =
クッキーから送られたセッションIDの取得
proc getToken*(self:Client):Future[string] {.async.} =
セッションDBのフラッシュデータへのアクセス
proc setFlash*(self:Client, key, value:string) {.async.} =
proc setFlash*(self:Client, key:string, value:JsonNode) {.async.} =
proc hasFlash*(self:Client, key:string):Future[bool] {.async.} =
proc getFlash*(self:Client):Future[JsonNode] {.async.} =
proc getValidationResult*(self:Client):Future[tuple[params:JsonNode, errors:JsonNode]] {.async.} =
MPA(Multi page application)の時のインスタンス作成
proc index(request:Request, params:Params):Future[Response] {.async.} =
let client = await newClient(request)
APIの時のインスタンス作成
proc index(request:Request, params:Params):Future[Response] {.async.} =
let sessionId = request.headers["x-login-token"]
let client = await newClient(sessionId)
ログイン
proc index(request:Request, params:Params):Future[Response] {.async.} =
let email = params.getStr("email")
let password = params.getStr("password")
let userId = newLoginUsecase().login(email, password)
let client = await newClient(request)
await client.login()
await client.set("id", $userId)
return redirect("/")
ログアウト
proc index(request:Request, params:Params):Future[Response] {.async.} =
let client = await newClient(request)
if await client.isLogin():
await client.logout()
redirect("/")
セッションから値を取得する
proc index(request:Request, params:Params):Future[Response] {.async.} =
let client = await newClient(request)
let loginName = await client.get("login_name")
セッションに値を保存する
proc index(request:Request, params:Params):Future[Response] {.async.} =
let name = params.getStr("name")
let client = await newClient(request)
await client.set("login_name", name)
return render("auth")
セッションに値が存在するかチェックして取得する
proc index(request:Request, params:Params):Future[Response] {.async.} =
var loginName:string
let client = await newClient(reques)
if await client.some("login_name"):
loginName = await client.get("login_name")
セッションの1つの値を削除する
proc destroy(request:Request, params:Params):Future[Response] {.async.} =
let client = await newClient(request)
await client.delete("login_name")
return render("auth")
クライアントに紐付いた全てのセッションデータを削除する
proc destroy(request:Request, params:Params):Future[Response] {.async.} =
let client = await newClient(request)
await client.destroy()
return render("auth")
フラッシュメッセージを保存する
proc store*(request:Request, params:Params):Response =
let client = await newClient(request)
await client.setFlash("success", "Welcome to the Sample App!")
return redirect("/auth")
フラッシュメッセージを取得する
proc show*(self:Controller):Response =
let client = await newClient(request)
let flash = await client.getFlash("success")
let user = newUserUsecase().show()
return render(showHtml(user, flash))
.env
のENABLE_ANONYMOUS_COOKIE
にtrue
を設定すると、Basolatoは全てのクライアントに自動的にクッキーを発行します。
false
を設定しかつログイン機能を有効にしたい場合は、自作してください。
.env
ENABLE_ANONYMOUS_COOKIE=true
controller
proc signIn*(request:Request, params:Params):Future[Response] {.async.} =
let email = params.getStr("email")
let password = params.getStr("password")
# ..sign in check
let client = await newClient(request)
await client.login()
return redirect("/")
.env
ENABLE_ANONYMOUS_COOKIE=false
controller
proc signIn*(request:Request, params:Params):Future[Response] {.async.} =
let email = params.getStr("email")
let password = params.getStr("password")
# ..sign in check
let client = await newClient(request)
await client.login()
return await redirect("/").setCookie(client)
.env
の設定で、クッキーのドメインを複数定義することができます。
.env
COOKIE_DOMAINS="nim-lang.org, github.com"
Google Chrome
はクッキーのドメイン「localhost」を許可していないので、localhost用のクッキーを作成したい場合は、以下のように設定してください。
COOKIE_DOMAINS=", nim-lang.org, github.com"
⚠ ほとんどの場合、SessionとCookiesは直接使用すべきではなく、Clientを使用するべきです。 ⚠
type
CookieData* = ref object
name: string
value: string
expire: string
sameSite: SameSite
secure: bool
httpOnly: bool
domain: string
path: string
Cookie* = ref object
request: Request
cookies*: seq[CookieData]
proc newCookie*(request:Request):Cookie =
proc get*(self:Cookie, name:string):string =
proc set*(self:var Cookie, name, value: string, expire:DateTime,
sameSite: SameSite=Lax, secure = false, httpOnly = false, domain = "",
path = "/") =
proc set*(self:var Cookie, name, value: string, sameSite: SameSite=Lax,
secure = false, httpOnly = false, domain = "", path = "/") =
proc updateExpire*(self:var Cookie, name:string, num:int, timeUnit:TimeUnit, path="/") =
proc updateExpire*(self:var Cookie, num:int, time:TimeUnit) =
proc delete*(self:Cookie, key:string, path="/"):Cookie =
proc destroy*(self:Cookie, path="/"):Cookie =
proc setCookie*(response:Response, cookie:Cookie):Response =
クッキーの値を取得する
proc index(request:Request, params:Params):Future[Response] {.async.} =
let val = newCookie(request).get("key")
クッキーに値を保存する
proc store*(request:Request, params:Params):Future[Response] {.async.} =
let name = params.getStr("name")
var cookie = newCookie(request)
cookie.set("name", name)
return render("with cookie").setCookie(cookie)
クッキーの有効期限を更新する
proc store*(request:Request, params:Params):Future[Response] {.async.} =
var cookie = newCookie(request)
cookie.updateExpire("name", 5)
# cookie will be deleted after 5 days from now
return render("with cookie").setCookie(cookie)
指定したキーのクッキーを削除する
proc index(request:Request, params:Params):Future[Response] {.async.} =
var cookie = newCookie(request)
cookie.delete("key")
return render("with cookie").setCookie(cookie)
全てのクッキーを削除する
proc index(request:Request, params:Params):Future[Response] {.async.} =
var cookie = newCookie(request)
cookie.destroy()
return render("with cookie").setCookie(cookie)
Secure
とHttpOnly
が設定されているので、JavaScriptでは読み込まれず、HTTPSでのみ使用できます。
Basolatoはファイルセッションのデータベースにはflatdbを使っています。
newSession()
の引数にsessionId
を設定すると、既存のセッションを返し、そうでなければ新しいセッションを作成します。
Session* = ref object
db: SessionDb
proc newSession*(token=""):Future[Session] {.async.} =
# 有効なトークンをセットすれば、存在してるセッションと接続します
# 無効なトークンをセットすれば、新しくセッションを作ります
proc getToken*(self:Session):Future[string] {.async.} =
proc set*(self:Session, key, value:string) {.async.} =
proc some*(self:Session, key:string):Future[bool] {.async.} =
proc get*(self:Session, key:string):Future[string] {.async.} =
proc delete*(self:Session, key:string) {.async.} =
proc destroy*(self:Session) {.async.} =
セッションIDを取得する
proc index(request:Request, params:Params):Future[Response] {.async.} =
let sessionId = newSession().getToken()
セッションに値を保存する
proc store(request:Request, params:Params):Future[Response] {.async.} =
let key = request.getStr("key")
let value = request.getStr("value")
discard newSession().set(key, value)
セッションに値が存在するかチェックして取得する
proc index(self:Controller):Future[Response] {.async.} =
let sessionId = newCookie(self.request).get("session_id")
let key = self.request.params["key"]
let session = newSession(sessionId)
var value:string
if session.some(key):
value = session.get(key)
セッションの1つの値を削除する
proc destroy(self:Controller):Future[Response] {.async.} =
let sessionId = newCookie(self.request).getToken()
let key = self.request.params["key"]
discard newSession(sessionId).delete(key)
クライアントに紐付いた全てのセッションデータを削除する
proc destroy(self:Controller):Future[Response] {.async.} =
let sessionId = newCookie(self.request).getToken()
newSession(sessionId).destroy()