Skip to content
This repository has been archived by the owner on Jan 3, 2025. It is now read-only.

Commit

Permalink
Fix more bugs found during testing on staging (#286)
Browse files Browse the repository at this point in the history
* correctly import the polling

* unhardcode the routes

* at v2 to competitions

* add cors headers to lambda

* correctly add attendee_id to polling url

* remove unnecessary eslint comment

* fix event_change_deadline being null breaking the validate events check

* corrected the headers in lambda

* Correctly assemble attendee_id for polling

* correctly return competing_status wrapped in { competing }

* correctly read competition info from the right WCA_HOST

* add null guard to data

* remove payment_init path as it is local

* correctly find competition in payment_init

* check if registration is nil when trying to generate a payment ticket

* use EnvConfig when accessing WCA_HOST

* add https to url

* add missing JSON.stringify

* correctly name paymentId to id

* update correct name for payment_id

* send the data in a ruby_snake_case

* correctly set user

* put attendee_id as get parameter

* create update_payment_status route

* return payment_status on the frontend

* run rubocop

* add WCA_HOST to dev and test

* handle payment being done

* correctly return payment_status
  • Loading branch information
FinnIckler authored Oct 26, 2023
1 parent 374ff3c commit 61f9715
Show file tree
Hide file tree
Showing 22 changed files with 190 additions and 53 deletions.
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ DYNAMO_REGISTRATIONS_TABLE=registrations-development
RAILS_LOG_TO_STDOUT=true
JWT_SECRET=jwt-test-secret
SECRET_KEY_BASE=a003fdc6f113ff7d295596a02192c7116a76724ba6d3071043eefdd16f05971be0dc58f244e67728757b2fb55ae7a41e1eb97c1fe247ddaeb6caa97cea32120c
WCA_HOST=worldcubeassociation.org
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ AWS_SECRET_ACCESS_KEY=fake-access-key
DYNAMO_REGISTRATIONS_TABLE=registrations-development
JWT_SECRET=jwt-test-secret
SECRET_KEY_BASE=a003fdc6f113ff7d295596a02192c7116a76724ba6d3071043eefdd16f05971be0dc58f244e67728757b2fb55ae7a41e1eb97c1fe247ddaeb6caa97cea32120c
WCA_HOST=worldcubeassociation.org
5 changes: 0 additions & 5 deletions Frontend/src/api/helper/backend_fetch.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { getJWT } from '../auth/get_jwt'
import { UpdateRegistrationBody } from '../types'
import { EXPIRED_TOKEN } from './error_codes'

type Method = 'POST' | 'GET' | 'PATCH' | 'DELETE'

type Body = UpdateRegistrationBody

export class BackendError extends Error {
errorCode: number
httpCode: number
Expand Down Expand Up @@ -46,8 +43,6 @@ export default async function backendFetch(
headers,
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This is injected at build time
const response = await fetch(`${process.env.API_URL}/${route}`, init)
// We always return a json error message, even on error
const body = await response.json()
Expand Down
14 changes: 6 additions & 8 deletions Frontend/src/api/helper/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ export const tokenRoute = `${process.env.WCA_URL}/api/v0/users/token`
export const permissionsRoute = `${process.env.WCA_URL}/api/v0/users/me/permissions`
export const paymentConfigRoute = `${process.env.WCA_URL}/payment/config`
export const paymentFinishRoute = (competitionId: string, userId: string) =>
`${process.env.WCA_URL}/payment/${competitionId}-${userId}/finish`
// TODO: Move this to swagger once finalised
export const paymentIdRoute = (id: string) =>
`${process.env.API_URL}/${id}/payment`
`${process.env.WCA_URL}/payment/finish?attendee_id=${competitionId}-${userId}`
export const pollingRoute = (userId: string, competitionId: string) =>
`${process.env.POLL_URL}?attendee_id=${competitionId}-${userId}`
export const meRoute = `${process.env.WCA_URL}/api/v0/users/me`
// This will break when urls get really big, maybe we should switch to POST?
export const usersInfoRoute = (ids: string[]) =>
`${process.env.WCA_URL}/api/v0/users?${ids
.map((id) => 'ids[]=' + id)
.join('&')}`
// Hardcoded because these are currently not mocked
export const competitionInfoRoute = (id: string) =>
`https://api.worldcubeassociation.org/competitions/${id}`
`${process.env.WCA_URL}/api/v0/competitions/${id}`
export const competitionWCIFRoute = (id: string) =>
`https://api.worldcubeassociation.org/competitions/${id}/wcif/public`
`${process.env.WCA_URL}/api/v0/competitions/${id}/wcif/public`
export const userInfoRoute = (id: string) =>
`https://api.worldcubeassociation.org/users/${id}`
`${process.env.WCA_URL}/api/v0/users/${id}`
8 changes: 4 additions & 4 deletions Frontend/src/api/payment/get/get_stripe_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export default async function getStripeConfig(
return externalServiceFetch(paymentConfigRoute, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: {
competitionId,
paymentId,
},
body: JSON.stringify({
competition_id: competitionId,
payment_id: paymentId,
}),
})
}
6 changes: 3 additions & 3 deletions Frontend/src/api/registration/get/get_payment_intent.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import backendFetch from '../../helper/backend_fetch'
import { paymentIdRoute } from '../../helper/routes'

export interface PaymentInfo {
// This is the MySQL payment id that can be give to the payment service
// to get the relevant data, not the Stripe ID!
payment_id: string
id: string
status: string
}
// We get the user_id out of the JWT key, which is why we only send the
// competition_id
export default async function getPaymentId(
competitionId: string
): Promise<PaymentInfo> {
return backendFetch(paymentIdRoute(competitionId), 'GET', {
return backendFetch(`/${competitionId}/payment`, 'GET', {
needsAuthentication: true,
}) as Promise<PaymentInfo>
}
12 changes: 7 additions & 5 deletions Frontend/src/api/registration/get/poll_registrations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// import externalServiceFetch from '../../helper/external_service_fetch'
import externalServiceFetch from '../../helper/external_service_fetch'
import { pollingRoute } from '../../helper/routes'
import pollingMock from '../../mocks/polling_mock'

export interface RegistrationStatus {
Expand All @@ -9,11 +10,12 @@ export interface RegistrationStatus {
queue_count: number
}

export async function pollRegistrations(): Promise<RegistrationStatus> {
export async function pollRegistrations(
userId: string,
competitionId: string
): Promise<RegistrationStatus> {
if (process.env.NODE_ENV === 'production') {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore inject at build time
return externalServiceFetch(process.env.POLL_URL)
return externalServiceFetch(pollingRoute(userId, competitionId))
}
return pollingMock()
}
4 changes: 2 additions & 2 deletions Frontend/src/pages/register/components/PaymentStep.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function PaymentStep() {
const elements = useElements()
const [isLoading, setIsLoading] = useState(false)
const { competitionInfo } = useContext(CompetitionContext)
const { userInfo } = useContext(UserContext)
const { user } = useContext(UserContext)
const handleSubmit = async (e) => {
e.preventDefault()

Expand All @@ -25,7 +25,7 @@ export default function PaymentStep() {
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: paymentFinishRoute(competitionInfo.id, userInfo.id),
return_url: paymentFinishRoute(competitionInfo.id, user.id),
},
})

Expand Down
10 changes: 7 additions & 3 deletions Frontend/src/pages/register/components/Processing.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { useQuery } from '@tanstack/react-query'
import React, { useEffect, useState } from 'react'
import React, { useContext, useEffect, useState } from 'react'
import { Message } from 'semantic-ui-react'
import { CompetitionContext } from '../../../api/helper/context/competition_context'
import { UserContext } from '../../../api/helper/context/user_context'
import { pollRegistrations } from '../../../api/registration/get/poll_registrations'

const REFETCH_INTERVAL = 3000

export default function Processing({ onProcessingComplete }) {
const [pollCounter, setPollCounter] = useState(0)
const { competitionInfo } = useContext(CompetitionContext)
const { user } = useContext(UserContext)
const { data } = useQuery({
queryKey: ['registration-status-polling'],
queryFn: async () => pollRegistrations(),
queryKey: ['registration-status-polling', user.id, competitionInfo.id],
queryFn: async () => pollRegistrations(user.id, competitionInfo.id),
refetchInterval: REFETCH_INTERVAL,
onSuccess: () => {
setPollCounter(pollCounter + 1)
Expand Down
12 changes: 8 additions & 4 deletions Frontend/src/pages/register/components/StripeWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function StripeWrapper() {
const [stripePromise, setStripePromise] = useState(null)
const { competitionInfo } = useContext(CompetitionContext)
const {
data,
data: paymentInfo,
isLoading: isPaymentIdLoading,
isError,
} = useQuery({
Expand All @@ -37,10 +37,11 @@ export default function StripeWrapper() {
})

const { data: config, isLoading: isConfigLoading } = useQuery({
queryKey: ['payment-config', competitionInfo.id, data.payment_id],
queryFn: () => getStripeConfig(competitionInfo.id, data.payment_id),
queryKey: ['payment-config', competitionInfo.id, paymentInfo?.id],
queryFn: () => getStripeConfig(competitionInfo.id, paymentInfo?.id),
onError: (err) => setMessage(err.error, 'error'),
enabled: !isPaymentIdLoading && !isError,
enabled:
!isPaymentIdLoading && !isError && paymentInfo?.status !== 'succeeded',
refetchOnWindowFocus: false,
refetchOnReconnect: false,
staleTime: Infinity,
Expand All @@ -64,6 +65,9 @@ export default function StripeWrapper() {
return (
<>
<h1>Payment</h1>
{paymentInfo?.status === 'succeeded' && (
<div>Your payment has been successfully processed.</div>
)}
{!isPaymentIdLoading && stripePromise && !isError && (
<Elements
stripe={stripePromise}
Expand Down
13 changes: 7 additions & 6 deletions Frontend/src/ui/Tabs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { BASE_ROUTE } from '../routes'
import styles from './tabs.module.scss'

function pathMatch(name, pathname) {
const registerExpression = /\/competitions\/[a-zA-Z0-9]+\/register/
const registerExpression = /\/competitions\/v2\/[a-zA-Z0-9]+\/register/
const registrationsExpression =
/\/competitions\/[a-zA-Z0-9]+\/registrations\/edit/
const competitorsExpression = /\/competitions\/[a-zA-Z0-9]+\/registrations/
const eventsExpressions = /\/competitions\/[a-zA-Z0-9]+\/events/
const scheduleExpressions = /\/competitions\/[a-zA-Z0-9]+\/schedule/
const infoExpression = /\/competitions\/[a-zA-Z0-9]+$/
/\/competitions\/v2\/[a-zA-Z0-9]+\/registrations\/edit/
const competitorsExpression =
/\/competitions\/v2\/[a-zA-Z0-9]+\/registrations/
const eventsExpressions = /\/competitions\/v2\/[a-zA-Z0-9]+\/events/
const scheduleExpressions = /\/competitions\/v2\/[a-zA-Z0-9]+\/schedule/
const infoExpression = /\/competitions\/v2\/[a-zA-Z0-9]+$/
switch (name) {
case 'register':
return registerExpression.test(pathname)
Expand Down
46 changes: 46 additions & 0 deletions app/controllers/internal_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

require 'time'

class InternalController < ApplicationController
prepend_before_action :validate_token

def validate_token
service_token = request.headers['X-WCA-Service-Token']
unless service_token.present?
return render json: { error: 'Missing Authentication' }, status: :forbidden
end
# The Vault CLI can't parse the response from identity/oidc/introspect so
# we need to request it instead see https://github.com/hashicorp/vault/issues/9080

vault_token_data = Vault.auth_token.lookup_self.data
# Renew our token if it has expired or is close to expiring
if vault_token_data[:ttl] < 300
Vault.auth_token.renew_self
end

# Make the POST request to the introspect endpoint
response = HTTParty.post("#{EnvConfig.VAULT_ADDR}/v1/identity/oidc/introspect",
body: { token: service_token }.to_json,
headers: { 'X-Vault-Token' => vault_token_data[:id],
'Content-Type' => 'application/json' })
if response.ok?
unless response['active']
render json: { error: 'Authentication Expired or Token Invalid' }, status: :forbidden
end
else
raise "Introspection failed with the following error: #{response.status}, #{response.body}"
end
end

def update_payment_status
attendee_id = params.require(:attendee_id)
payment_id = params.require(:payment_id)
iso_amount = params.require(:iso_amount)
currency_iso = params.require(:currency_iso)
payment_status = params.require(:payment_status)
registration = Registration.find(attendee_id)
registration.update_payment_lane(payment_id, iso_amount, currency_iso, payment_status)
render json: { status: 'ok' }
end
end
19 changes: 15 additions & 4 deletions app/controllers/registration_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,16 @@ def payment_ticket
else
ticket = @registration.payment_ticket
end
render json: { id: ticket }
render json: { id: ticket, status: @registration.payment_status }
end

def validate_payment_ticket_request
competition_id = params[:competition_id]
@competition = CompetitionApi.find!(competition_id)
render_error(:forbidden, ErrorCodes::PAYMENT_NOT_ENABLED) unless @competition.using_wca_payment?

@registration = Registration.find("#{competition_id}-#{@current_user}")
render_error(:forbidden, ErrorCodes::PAYMENT_NOT_READY) if @registration.competing_state.nil?
render_error(:forbidden, ErrorCodes::PAYMENT_NOT_READY) if @registration.nil? || @registration.competing_status.nil?
end

def list
Expand Down Expand Up @@ -258,6 +259,10 @@ def get_registrations(competition_id, only_attending: false)
comment: x.competing_comment,
admin_comment: x.admin_comment,
},
payment: {
payment_status: x.payment_status,
updated_at: x.payment_date,
},
guests: x.guests }
end
end
Expand All @@ -275,6 +280,10 @@ def get_single_registration(user_id, competition_id)
comment: registration.competing_comment,
admin_comment: registration.admin_comment,
},
payment: {
payment_status: registration.payment_status,
updated_at: registration.payment_date,
},
}
end

Expand Down Expand Up @@ -302,8 +311,10 @@ def validate_events!

# Events can't be changed outside the edit_events deadline
# TODO: Should an admin be able to override this?
events_edit_deadline = Time.parse(@competition.event_change_deadline)
raise RegistrationError.new(:forbidden, ErrorCodes::EVENT_EDIT_DEADLINE_PASSED) if events_edit_deadline < Time.now
if @competition.event_change_deadline.present?
events_edit_deadline = Time.parse(@competition.event_change_deadline)
raise RegistrationError.new(:forbidden, ErrorCodes::EVENT_EDIT_DEADLINE_PASSED) if events_edit_deadline < Time.now
end
end

def admin_fields_present?
Expand Down
3 changes: 1 addition & 2 deletions app/helpers/competition_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
require_relative 'wca_api'

def comp_api_url(competition_id)
comp_api_baseurl = 'https://worldcubeassociation.org/api/v0/competitions'
"#{comp_api_baseurl}/#{competition_id}"
"https://#{EnvConfig.WCA_HOST}/api/v0/competitions/#{competition_id}"
end

class CompetitionApi < WcaApi
Expand Down
6 changes: 5 additions & 1 deletion app/helpers/lane_factory.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'time'

class LaneFactory
def self.competing_lane(event_ids = [], comment = '', guests = 0)
competing_lane = Lane.new({})
Expand All @@ -21,8 +23,10 @@ def self.payment_lane(fee_lowest_denominator, currency_code, payment_id)
payment_lane.lane_state = 'initialized'
payment_lane.lane_details = {
amount_lowest_denominator: fee_lowest_denominator,
payment_intent_client_secret: payment_id,
payment_id: payment_id,
currency_code: currency_code,
last_updated: Time.now,
payment_history: [],
}
payment_lane
end
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/payment_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def self.get_ticket(attendee_id, amount, currency_code)

class << self
def payment_init_path
"#{WCA_HOST}/api/internal/v1/payment/init"
"https://#{EnvConfig.WCA_HOST}/api/internal/v1/payment/init"
end
end
end
Loading

0 comments on commit 61f9715

Please sign in to comment.