feat: extend renderable with csrf and csrf_token
This commit is contained in:
1 parent
b9fc8a09d2
commit
721d576956
3 files changed
+182
-5
No files matched your search
+71
-1
@@ -13,7 +13,22 @@ module Prism
|
|||||||
end
|
end
|
||||||
|
|
||||||
def cookies
|
def cookies
|
||||||
@request.cookies
|
@cookie_jar ||= begin
|
||||||
|
secret = @env['prism.secret']
|
||||||
|
if secret
|
||||||
|
CookieJar.new(@request, @response, secret: secret, previous_secrets: @env['prism.previous_secrets'] || [])
|
||||||
|
else
|
||||||
|
@request.cookies
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def csrf
|
||||||
|
@csrf ||= Csrf.new(session)
|
||||||
|
end
|
||||||
|
|
||||||
|
def csrf_token
|
||||||
|
csrf.masked_token
|
||||||
end
|
end
|
||||||
|
|
||||||
def status(code = nil)
|
def status(code = nil)
|
||||||
@@ -34,6 +49,21 @@ module Prism
|
|||||||
payload
|
payload
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# JSON:API-lite error response for a failed-validation record: `{
|
||||||
|
# "errors": [...] }` with each entry built by Orm::Errors#to_jsonapi.
|
||||||
|
# Mirrors `render_errors(post)` -> `format.json` the same way `render`
|
||||||
|
# covers the HTML branch of a respond_to block.
|
||||||
|
def render_errors(record, status: 422)
|
||||||
|
json({ errors: record.errors.to_jsonapi(status: status) }, status)
|
||||||
|
end
|
||||||
|
|
||||||
|
# JSON:API-lite "not found" error, the 404 counterpart to render_errors.
|
||||||
|
# No `source.pointer` (unlike a validation error, a missing resource
|
||||||
|
# does not point at one attribute of it).
|
||||||
|
def render_not_found(detail, status: 404)
|
||||||
|
json({ errors: [{ status: status.to_s, title: 'Resource Not Found', detail: detail }] }, status)
|
||||||
|
end
|
||||||
|
|
||||||
def html(markup, code = nil)
|
def html(markup, code = nil)
|
||||||
@response.status = code if code
|
@response.status = code if code
|
||||||
@response['Content-Type'] = 'text/html;charset=utf-8'
|
@response['Content-Type'] = 'text/html;charset=utf-8'
|
||||||
@@ -41,6 +71,15 @@ module Prism
|
|||||||
markup
|
markup
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ActionController::Live-lite: `stream { |out| out.write(row) }` sends
|
||||||
|
# the response as HTTP/1.1 chunked Transfer-Encoding instead of
|
||||||
|
# buffering the whole body first - see Prism::StreamingBody and
|
||||||
|
# Server#write_response. Nothing inside the block runs here; it only
|
||||||
|
# runs once the server starts sending the response (see Response#stream).
|
||||||
|
def stream(&block)
|
||||||
|
@response.stream(&block)
|
||||||
|
end
|
||||||
|
|
||||||
def redirect(location, code = 302)
|
def redirect(location, code = 302)
|
||||||
@response.status = code
|
@response.status = code
|
||||||
@response['Location'] = location
|
@response['Location'] = location
|
||||||
@@ -73,5 +112,36 @@ module Prism
|
|||||||
def finalize_flash
|
def finalize_flash
|
||||||
@flash.sweep if @flash
|
@flash.sweep if @flash
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Locale for this request: an explicit ?locale= param wins, then a
|
||||||
|
# sticky value stashed in the session, then the client's
|
||||||
|
# Accept-Language header, then Prism::I18n.default_locale.
|
||||||
|
def locale
|
||||||
|
@locale ||= detect_locale
|
||||||
|
end
|
||||||
|
|
||||||
|
def translate(key, **options)
|
||||||
|
Prism::I18n.t(key, locale: options.delete(:locale) || locale, **options)
|
||||||
|
end
|
||||||
|
alias_method :t, :translate
|
||||||
|
|
||||||
|
def localize(value, **options)
|
||||||
|
Prism::I18n.l(value, locale: options.delete(:locale) || locale, **options)
|
||||||
|
end
|
||||||
|
alias_method :l, :localize
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def detect_locale
|
||||||
|
requested = params['locale'] || session[:locale]
|
||||||
|
return requested.to_sym if requested && available_locale?(requested)
|
||||||
|
|
||||||
|
negotiated = Prism::I18n.best_locale_from_accept(@request.headers['Accept-Language'])
|
||||||
|
negotiated || Prism::I18n.default_locale
|
||||||
|
end
|
||||||
|
|
||||||
|
def available_locale?(candidate)
|
||||||
|
Prism::I18n.available_locales.map(&:to_s).include?(candidate.to_s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
+34
-4
@@ -6,8 +6,10 @@ module Prism
|
|||||||
@env = env
|
@env = env
|
||||||
end
|
end
|
||||||
|
|
||||||
|
OverridableMethods = %w[PUT PATCH DELETE].freeze
|
||||||
|
|
||||||
def request_method
|
def request_method
|
||||||
@env['REQUEST_METHOD']
|
@request_method ||= compute_request_method
|
||||||
end
|
end
|
||||||
|
|
||||||
def path
|
def path
|
||||||
@@ -22,10 +24,20 @@ module Prism
|
|||||||
@env['CONTENT_TYPE']
|
@env['CONTENT_TYPE']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_agent
|
||||||
|
@env['HTTP_USER_AGENT']
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_addr
|
||||||
|
@env['REMOTE_ADDR']
|
||||||
|
end
|
||||||
|
|
||||||
def body
|
def body
|
||||||
@body ||= begin
|
@body ||= begin
|
||||||
input = @env['rack.input']
|
input = @env['rack.input']
|
||||||
input ? input.read : ''
|
content = input ? input.read : ''
|
||||||
|
input.rewind if input.respond_to?(:rewind)
|
||||||
|
content
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -67,11 +79,27 @@ module Prism
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Negotiated response format: an explicit `.json` path/query extension
|
||||||
|
# wins, otherwise it falls back to the Accept header, defaulting to
|
||||||
|
# :html when nothing else matches (mirrors Rails' request.format).
|
||||||
|
def format
|
||||||
|
explicit = path_params['format'] || query_params['format']
|
||||||
|
return explicit.to_s.to_sym unless explicit.nil? || explicit.to_s.empty?
|
||||||
|
Mime.best_from_accept(headers['Accept']) || :html
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def compute_request_method
|
||||||
|
actual = @env['REQUEST_METHOD']
|
||||||
|
return actual unless actual == 'POST'
|
||||||
|
override = (@env['HTTP_X_HTTP_METHOD_OVERRIDE'] || body_params['_method']).to_s.upcase
|
||||||
|
OverridableMethods.include?(override) ? override : actual
|
||||||
|
end
|
||||||
|
|
||||||
def parse_query(string)
|
def parse_query(string)
|
||||||
return Params.new if string.empty?
|
return Params.new if string.empty?
|
||||||
Params.from(URI.decode_www_form(string).to_h)
|
Params.nested_from(URI.decode_www_form(string))
|
||||||
rescue ArgumentError
|
rescue ArgumentError
|
||||||
Params.new
|
Params.new
|
||||||
end
|
end
|
||||||
@@ -82,7 +110,9 @@ module Prism
|
|||||||
parsed = JSON.parse(body)
|
parsed = JSON.parse(body)
|
||||||
parsed.is_a?(Hash) ? Params.from(parsed) : Params.from('_json' => parsed)
|
parsed.is_a?(Hash) ? Params.from(parsed) : Params.from('_json' => parsed)
|
||||||
elsif content_type&.include?('application/x-www-form-urlencoded')
|
elsif content_type&.include?('application/x-www-form-urlencoded')
|
||||||
Params.from(URI.decode_www_form(body).to_h)
|
Params.nested_from(URI.decode_www_form(body))
|
||||||
|
elsif Multipart.multipart?(content_type)
|
||||||
|
Multipart.parse(body, Multipart.boundary_from(content_type))
|
||||||
else
|
else
|
||||||
Params.new
|
Params.new
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
module Prism
|
||||||
|
# DSL context yielded (via instance_eval) inside `resources ... do ... end`
|
||||||
|
# and `resource ... do ... end` blocks. Provides member/collection routes
|
||||||
|
# and nesting further resources under the current one.
|
||||||
|
class ResourceScope
|
||||||
|
def initialize(app, name, singular, param)
|
||||||
|
@app = app
|
||||||
|
@name = name
|
||||||
|
@singular = singular
|
||||||
|
@param = param
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extra actions scoped to one member: /name/:id/action -> name#action,
|
||||||
|
# named "action_singular" (e.g. `publish_post`).
|
||||||
|
def member(&block)
|
||||||
|
require_member_param!
|
||||||
|
@app.group("/#{@name}/:#{@param}") do
|
||||||
|
MemberCollectionScope.new(@app, @singular, @name, :member).instance_eval(&block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extra actions scoped to the whole collection: /name/action -> name#action,
|
||||||
|
# named "action_name" (e.g. `search_posts`).
|
||||||
|
def collection(&block)
|
||||||
|
@app.group("/#{@name}") do
|
||||||
|
MemberCollectionScope.new(@app, @singular, @name, :collection).instance_eval(&block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Nested resources, scoped under this resource: for a plural parent
|
||||||
|
# that's /name/:singular_id/nested_name/... (Rails' classic nested
|
||||||
|
# routing); for a singular parent (no id at all) it's just
|
||||||
|
# /name/nested_name/....
|
||||||
|
def resources(nested_name, **options, &block)
|
||||||
|
@app.group(nested_prefix) { @app.resources(nested_name, **options, &block) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource(nested_name, **options, &block)
|
||||||
|
@app.group(nested_prefix) { @app.resource(nested_name, **options, &block) }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def nested_prefix
|
||||||
|
@param ? "/#{@name}/:#{@singular}_id" : "/#{@name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_member_param!
|
||||||
|
return if @param
|
||||||
|
raise ArgumentError, 'member/collection routes need an id segment; not available on a singular resource'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# DSL context for the block passed to ResourceScope#member/#collection.
|
||||||
|
class MemberCollectionScope
|
||||||
|
def initialize(app, singular, plural, kind)
|
||||||
|
@app = app
|
||||||
|
@singular = singular
|
||||||
|
@plural = plural
|
||||||
|
@kind = kind
|
||||||
|
end
|
||||||
|
|
||||||
|
%w[get post put patch delete].each do |verb|
|
||||||
|
define_method(verb) do |action, to: nil, as: nil|
|
||||||
|
controller_action = to || "#{@plural}##{action}"
|
||||||
|
name = as || default_name(action)
|
||||||
|
@app.public_send(verb, "/#{action}", to: controller_action, as: name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def default_name(action)
|
||||||
|
@kind == :member ? "#{action}_#{@singular}" : "#{action}_#{@plural}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user