用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/kemalcr/kemal --skill kemal-auth命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Use when building, reviewing, debugging, testing, securing, or deploying web applications and HTTP APIs with the Kemal framework for Crystal. Covers Kemal routing, params, context, routers, filters, middleware, ECR, WebSockets, SSE, uploads, configuration, testing, and production concerns.
Core Kemal development (routing verbs, parameters, modular router, version gates, response helpers).
Database initialization and interaction with SQLite and raw SQL in Kemal, following established project patterns.
正在显示 SKILL.md
| name | kemal-auth |
| description | User authentication and session management in Kemal, following established project patterns. |
| license | MIT |
This skill provides expert guidance on implementing user authentication and session management in Kemal, strictly following patterns from kemal-by-example/ecommerce and kemal-by-example/oauth-login.
Dependencies: Always require "kemal-session".
Session Configuration: Use Kemal::Session.config to set secret, cookie_name, and gc_interval:
Kemal::Session.config do |config|
config.secret = ENV["KEMAL_SESSION_SECRET"]? || raise "KEMAL_SESSION_SECRET not set"
config.cookie_name = "your_app_session"
config.gc_interval = 2.minutes
end
Auth Helpers: Implement auth logic in a module (e.g., Ecommerce::Auth):
current_user(env): Use env.session.bigint?("user_id") to retrieve the ID and find the userrequire_user(env): Call current_user(env) and redirect to /login if nilsign_in(env, user): Set env.session.bigint("user_id", user.id || raise "User ID required")sign_out(env): Call env.session.destroyPassword Hashing: Use Crypto::Bcrypt::Password for securely storing and authenticating passwords.
Error Handling: Use specific exception types (like DB::Error) in auth helpers.
def current_user(env) : User?
user_id = env.session.bigint?("user_id")
return unless user_id
User.find(user_id)
rescue DB::Error
nil
end
module Ecommerce
module Auth
extend self
def current_user(env) : User?
user_id = env.session.bigint?("user_id")
return unless user_id
User.find(user_id)
rescue DB::Error
nil
end
def require_user(env) : User?
user = current_user(env)
return user if user
env.redirect "/login"
nil
end
def sign_in(env, user : User)
user_id = user.id || raise ArgumentError.new("Cannot sign in user without ID")
env.session.bigint("user_id", user_id)
end
def sign_out(env)
env.session.destroy
end
end
end
From ecommerce and oauth-login:
Kemal::Session.config do |config|
config.secret = ENV["KEMAL_SESSION_SECRET"]? || raise "KEMAL_SESSION_SECRET not set"
config.cookie_name = "ecommerce_session_id"
config.gc_interval = 2.minutes
end
From ecommerce/src/models/user.cr:
require "crypto/bcrypt"
class User
include DB::Serializable
getter id : Int64?
getter name : String
getter email : String
getter password_hash : String
getter created_at : String
getter updated_at : String
def self.create(name : String, email : String, password : String) : User
now = Time.utc.to_s
normalized_email = normalize_email(email)
password_hash = Crypto::Bcrypt::Password.create(password, cost: 12).to_s
# ... insert and return user
end
def self.authenticate(email : String, password : String) : User?
user = find_by_email(normalize_email(email))
return unless user
return user if Crypto::Bcrypt::Password.new(user.password_hash).verify(password)
nil
end
def self.normalize_email(value : String) : String
value.strip.downcase
end
end
post "/login" do |env|
email = env.params.body["email"]?.try(&.strip) || ""
password = env.params.body["password"]?.try(&.strip) || ""
user = User.authenticate(email, password)
if user
Ecommerce::Auth.sign_in(env, user)
env.redirect "/products"
else
current_user = nil
cart_count = 0_i64
error_message = "Invalid email or password."
env.response.status = :unprocessable_entity
render "src/views/auth/login.ecr", "src/views/layouts/application.ecr"
end
end
post "/logout" do |env|
Ecommerce::Auth.sign_out(env)
env.redirect "/products"
end
render macro (e.g., error_message = "Invalid email or password.").POST (never GET) for login and logout so state changes are not triggerable via simple links — but note that using POST alone does not prevent CSRF. Add real CSRF protection: validate a per-session CSRF token on state-changing requests (e.g. the kemal-csrf handler) and set session cookies with SameSite. Regenerate the session on login to prevent session fixation.env.session.bigint?("user_id") or similar to safely retrieve session data.User.authenticate(email, password) and User.find_by_email(email) in the model.