Secure public contestant forms by asking the code first
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
sto
2026-08-09 10:09:04 +02:00
parent f9e100b85f
commit 84e3424fcf
6 changed files with 83 additions and 14 deletions

View File

@@ -7,7 +7,7 @@ class ContestantsController < ApplicationController
before_action :set_contest, only: %i[ index edit new create update destroy import upload_csv convert_csv finalize_import export generate_qrcodes generate_qrcodes_pdf generate_qrcodes_archive ] before_action :set_contest, only: %i[ index edit new create update destroy import upload_csv convert_csv finalize_import export generate_qrcodes generate_qrcodes_pdf generate_qrcodes_archive ]
before_action :set_contestant, only: %i[ destroy edit update] before_action :set_contestant, only: %i[ destroy edit update]
before_action :set_completions, only: %i[edit update ] before_action :set_completions, only: %i[edit update ]
skip_before_action :require_authentication, only: %i[ get_public_completion post_public_completion public_completion_updated ] skip_before_action :require_authentication, only: %i[ get_public_completion get_public_completion_code post_public_completion_code post_public_completion public_completion_updated ]
def index def index
authorize @contest authorize @contest
@@ -162,23 +162,74 @@ class ContestantsController < ApplicationController
def get_public_completion def get_public_completion
skip_authorization skip_authorization
@public = true
@contestant = Contestant.find(params[:contestant_id]) @contestant = Contestant.find(params[:contestant_id])
if !@contestant || !@contestant.contest.code.present? || !@contestant.contest.organizer_form if !@contestant || !@contestant.contest.code.present? || !@contestant.contest.organizer_form
not_found and return not_found and return
end end
I18n.locale = @contestant.contest.lang
@completion = Completion.new
@title = @contestant.contest.name
render "completions/validate_organizer_code"
end
def post_public_completion_code
skip_authorization
@public = true
@contestant = Contestant.find(params[:contestant_id])
if !@contestant || !@contestant.contest.code.present? || !@contestant.contest.organizer_form
not_found and return
end
I18n.locale = @contestant.contest.lang
@title = @contestant.contest.name
@completion = Completion.new(completion_params)
@completion.contest = @contestant.contest
@completion.contestant = @contestant
if !@completion.code.present?
to_modify = true
@completion.code = "incorrect-xZy"
end
@completion.save
if @completion.code != @contestant.contest.code
if to_modify
@completion.code = nil
end
render "completions/validate_organizer_code", status: :unprocessable_entity
else
redirect_to "/public/p/#{@contestant.id}/code?code=#{@completion.code}"
end
end
def get_public_completion_code
skip_authorization
@public = true
@contestant = Contestant.find(params[:contestant_id])
if !@contestant || !@contestant.contest.code.present? || !@contestant.contest.organizer_form
not_found and return
end
I18n.locale = @contestant.contest.lang
if !params.key?(:code) || params[:code] != @contestant.contest.code
redirect_to "/public/p/#{@contestant.id}" and return
end
@contest = @contestant.contest @contest = @contestant.contest
I18n.locale = @contest.lang
@puzzles = @contest.puzzles.where(hidden: false).or(@contest.puzzles.where(hidden: nil)).order(:id) @puzzles = @contest.puzzles.where(hidden: false).or(@contest.puzzles.where(hidden: nil)).order(:id)
@completion = Completion.new @completion = Completion.new
@completion.completed = true @completion.completed = true
@public = true @completion.code = @contest.code
render "completions/_form", locals: { completion: @completion, submit_text: t("helpers.buttons.create"), method: :post, url: "/public/p/#{params[:contestant_id]}" } render "completions/_form", locals: { completion: @completion, submit_text: t("helpers.buttons.create"), method: :post, url: "/public/p/#{params[:contestant_id]}" }
end end
def post_public_completion def post_public_completion
skip_authorization skip_authorization
@public = true
@contestant = Contestant.find(params[:contestant_id]) @contestant = Contestant.find(params[:contestant_id])
if !@contestant || !@contestant.contest.code.present? if !@contestant || !@contestant.contest.code.present?
@@ -188,21 +239,17 @@ class ContestantsController < ApplicationController
I18n.locale = @contest.lang I18n.locale = @contest.lang
@completion = Completion.new(completion_params) @completion = Completion.new(completion_params)
if @completion.code != @contest.code
redirect_to "/public/p/#{@contestant.id}" and return
end
@completion.contest = @contest @completion.contest = @contest
@completion.contestant = @contestant @completion.contestant = @contestant
if !@completion.code.present?
to_modify = true
@completion.code = "incorrect-xZy"
end
if @completion.save if @completion.save
extend_completions!(@completion.contestant) extend_completions!(@completion.contestant)
redirect_to "/public/p/#{params[:contestant_id]}/updated" redirect_to "/public/p/#{params[:contestant_id]}/updated"
else else
@puzzles = @contest.puzzles @puzzles = @contest.puzzles
@public = true
if to_modify
@completion.code = nil
end
render "completions/_form", locals: { completion: @completion, submit_text: t("helpers.buttons.create"), method: :post, url: "/public/p/#{params[:contestant_id]}" }, status: :unprocessable_entity render "completions/_form", locals: { completion: @completion, submit_text: t("helpers.buttons.create"), method: :post, url: "/public/p/#{params[:contestant_id]}" }, status: :unprocessable_entity
end end
end end

View File

@@ -126,9 +126,7 @@
.row.mb-3 .row.mb-3
.col .col
.form-floating .form-floating
= form.text_field :code, autocomplete: "off", class: "form-control" = form.hidden_field :code, autocomplete: "off", class: "form-control"
= form.label :code
= t("completions.form.code")
.row .row
.col .col
= form.submit submit_text, class: "btn btn-primary" = form.submit submit_text, class: "btn btn-primary"

View File

@@ -0,0 +1,20 @@
- if @public && @contestant.contest.puzzles.length == @contestant.completions.length
h4
= t("completions.form.validate_name", name: @contestant.name)
.mt-3.alert.alert-warning
= t("completions.form.all_finished", name: @contestant.name)
- else
.row.mt-2
.col
h4
= t("completions.form.validate_name", name: @contestant.name)
= form_with model: @completion, url: "/public/p/#{@contestant.id}/code", method: "post" do |form|
.row.mt-3
.col
.form-floating
= form.text_field :code, autocomplete: "off", class: "form-control"
= form.label :code
= t("completions.form.code")
.row.mt-3
.col
= form.submit t("helpers.buttons.validate"), class: "btn btn-primary"

View File

@@ -349,6 +349,7 @@ en:
stopwatch_reset: Reset stopwatch_reset: Reset
stopwatch_start: Start stopwatch_start: Start
update: Save modifications update: Save modifications
validate: Validate
field: Field field: Field
none: No field selected none: No field selected
rank: Rank rank: Rank

View File

@@ -320,6 +320,7 @@ fr:
stopwatch_reset: Ré-initialiser stopwatch_reset: Ré-initialiser
stopwatch_start: Démarrer stopwatch_start: Démarrer
update: Enregistrer les modifications update: Enregistrer les modifications
validate: Valider
field: Champ field: Champ
none: Aucun champ sélectionné none: Aucun champ sélectionné
rank: Rang rank: Rang

View File

@@ -63,6 +63,8 @@ Rails.application.routes.draw do
get "public/:id/offline/:token/completed", to: "contests#offline_completed" get "public/:id/offline/:token/completed", to: "contests#offline_completed"
get "public/p/:contestant_id", to: "contestants#get_public_completion" get "public/p/:contestant_id", to: "contestants#get_public_completion"
post "public/p/:contestant_id", to: "contestants#post_public_completion" post "public/p/:contestant_id", to: "contestants#post_public_completion"
get "public/p/:contestant_id/code", to: "contestants#get_public_completion_code"
post "public/p/:contestant_id/code", to: "contestants#post_public_completion_code"
get "public/p/:contestant_id/updated", to: "contestants#public_completion_updated" get "public/p/:contestant_id/updated", to: "contestants#public_completion_updated"
direct :public_scoreboard do |contest| direct :public_scoreboard do |contest|