diff --git a/Frontend/package.json b/Frontend/package.json index dfbbf6de..d853b988 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -15,7 +15,7 @@ "scripts": { "build:types": "TYPES_ONLY=true node src/build.mjs", "build:dev": "NODE_ENV=\"development\" ASSET_PATH=\"/dist\" API_URL=\"http://localhost:3001/api/v1\" AUTH_URL=\"http://localhost:3001/jwt\" node src/build.mjs", - "build:staging": "POLL_URL=\"https://1rq8d7dif3.execute-api.us-west-2.amazonaws.com/v1/staging\" NODE_ENV=\"staging\" ASSET_PATH=\"https://d1qizdh27al0a7.cloudfront.net/staging/dist\" API_URL=\"https://staging.registration.worldcubeassociation.org/api/v1\" WCA_URL=\"https://staging.worldcubeassociation.org\" node src/build.mjs", + "build:staging": "POLL_URL=\"https://1rq8d7dif3.execute-api.us-west-2.amazonaws.com/v1/staging\" NODE_ENV=\"production\" ASSET_PATH=\"https://d1qizdh27al0a7.cloudfront.net/staging/dist\" API_URL=\"https://staging.registration.worldcubeassociation.org/api/v1\" WCA_URL=\"https://staging.worldcubeassociation.org\" node src/build.mjs", "build:prod": "POLL_URL=\"https://1rq8d7dif3.execute-api.us-west-2.amazonaws.com/v1/prod\" NODE_ENV=\"production\" ASSET_PATH=\"https://d1qizdh27al0a7.cloudfront.net/dist\" API_URL=\"https://registration.worldcubeassociation.org/api/v1\" WCA_URL=\"https://worldcubeassociation.org\" node src/build.mjs", "watch": "node src/watch.mjs", "lint": "eslint src --ext .js,.jsx,.ts,.tsx", diff --git a/Frontend/src/api/mocks/get_jwt.ts b/Frontend/src/api/mocks/get_jwt.ts index 813424b5..5e53ab84 100644 --- a/Frontend/src/api/mocks/get_jwt.ts +++ b/Frontend/src/api/mocks/get_jwt.ts @@ -8,7 +8,7 @@ export default async function getJWTMock(): Promise { const secret = new TextEncoder().encode('jwt-test-secret') const alg = 'HS256' const issuedAt = Date.now() - const jwt = await new jose.SignJWT({ data: { user_id: user } }) + const jwt = await new jose.SignJWT({ user_id: user }) .setProtectedHeader({ alg }) .setIssuedAt(issuedAt) .setJti(Base64.stringify(md5(`${secret}:${issuedAt}`))) diff --git a/Frontend/src/index.dev.jsx b/Frontend/src/index.dev.jsx index 0c0a0b11..7b3175a5 100644 --- a/Frontend/src/index.dev.jsx +++ b/Frontend/src/index.dev.jsx @@ -15,6 +15,7 @@ import Registrations from './pages/registrations' import Schedule from './pages/schedule' import TestLogin from './pages/test/login' import TestLogout from './pages/test/logout' +import { BASE_ROUTE } from './routes' import App from './ui/App' import Competition from './ui/Competition' import CustomTab from './ui/CustomTab' @@ -60,7 +61,7 @@ const router = createBrowserRouter([ ), }, { - path: '/competitions/:competition_id', + path: `${BASE_ROUTE}/:competition_id`, element: ( @@ -73,35 +74,35 @@ const router = createBrowserRouter([ ), children: [ { - path: '/competitions/:competition_id', + path: `${BASE_ROUTE}/:competition_id`, element: , }, { - path: '/competitions/:competition_id/events', + path: `${BASE_ROUTE}/:competition_id/events`, element: , }, { - path: '/competitions/:competition_id/schedule', + path: `${BASE_ROUTE}/:competition_id/schedule`, element: , }, { - path: '/competitions/:competition_id/register', + path: `${BASE_ROUTE}/:competition_id/register`, element: , }, { - path: '/competitions/:competition_id/tabs/:tab_id', + path: `${BASE_ROUTE}/:competition_id/tabs/:tab_id`, element: , }, { - path: '/competitions/:competition_id/registrations', + path: `${BASE_ROUTE}/:competition_id/registrations`, element: , }, { - path: '/competitions/:competition_id/:user_id/edit', + path: `${BASE_ROUTE}/:competition_id/:user_id/edit`, element: , }, { - path: '/competitions/:competition_id/registrations/edit', + path: `${BASE_ROUTE}/:competition_id/registrations/edit`, element: , }, ], diff --git a/Frontend/src/index.jsx b/Frontend/src/index.jsx index c35a814d..e24933ad 100644 --- a/Frontend/src/index.jsx +++ b/Frontend/src/index.jsx @@ -2,85 +2,10 @@ import '@thewca/wca-components/dist/index.esm.css' import React from 'react' import { createRoot } from 'react-dom/client' -import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom' -import { Container } from 'semantic-ui-react' -import Events from './pages/events' -import HomePage from './pages/home' -import Register from './pages/register' -import RegistrationAdministration from './pages/registration_administration' -import RegistrationEdit from './pages/registration_edit' -import Registrations from './pages/registrations' -import Schedule from './pages/schedule' -import App from './ui/App' -import Competition from './ui/Competition' -import CustomTab from './ui/CustomTab' -import FlashMessage from './ui/messages/flashMessage' -import PermissionsProvider from './ui/providers/PermissionsProvider' -import UserProvider from './ui/providers/UserProvider' -import PageTabs from './ui/Tabs' +import { createBrowserRouter, RouterProvider } from 'react-router-dom' +import routes from './routes' -const router = createBrowserRouter([ - { - path: '/competitions', - element: ( - - - - - - - ), - children: [ - { - path: '/competitions/:competition_id', - element: ( - - - - - - - - - ), - children: [ - { - path: '/competitions/:competition_id', - element: , - }, - { - path: '/competitions/:competition_id/events', - element: , - }, - { - path: '/competitions/:competition_id/schedule', - element: , - }, - { - path: '/competitions/:competition_id/register', - element: , - }, - { - path: '/competitions/:competition_id/tabs/:tab_id', - element: , - }, - { - path: '/competitions/:competition_id/registrations', - element: , - }, - { - path: '/competitions/:competition_id/:user_id/edit', - element: , - }, - { - path: '/competitions/:competition_id/registrations/edit', - element: , - }, - ], - }, - ], - }, -]) +const router = createBrowserRouter(routes) // Render the React component into the body of the monolith const root = createRoot(document.querySelector('#registration-app')) diff --git a/Frontend/src/pages/registration_administration/components/RegistrationAdministrationList.jsx b/Frontend/src/pages/registration_administration/components/RegistrationAdministrationList.jsx index 2078f450..ad50e88c 100644 --- a/Frontend/src/pages/registration_administration/components/RegistrationAdministrationList.jsx +++ b/Frontend/src/pages/registration_administration/components/RegistrationAdministrationList.jsx @@ -5,6 +5,7 @@ import { Link } from 'react-router-dom' import { Checkbox, Popup, Table } from 'semantic-ui-react' import { CompetitionContext } from '../../../api/helper/context/competition_context' import { getAllRegistrations } from '../../../api/registration/get/get_registrations' +import { BASE_ROUTE } from '../../../routes' import { setMessage } from '../../../ui/events/messages' import LoadingMessage from '../../../ui/messages/loadingMessage' import styles from './list.module.scss' @@ -276,7 +277,7 @@ function RegistrationAdministrationTable({ Edit diff --git a/Frontend/src/routes.jsx b/Frontend/src/routes.jsx new file mode 100644 index 00000000..59ff92f5 --- /dev/null +++ b/Frontend/src/routes.jsx @@ -0,0 +1,84 @@ +import React from 'react' +import { Outlet } from 'react-router-dom' +import { Container } from 'semantic-ui-react' +import Events from './pages/events' +import HomePage from './pages/home' +import Register from './pages/register' +import RegistrationAdministration from './pages/registration_administration' +import RegistrationEdit from './pages/registration_edit' +import Registrations from './pages/registrations' +import Schedule from './pages/schedule' +import App from './ui/App' +import Competition from './ui/Competition' +import CustomTab from './ui/CustomTab' +import FlashMessage from './ui/messages/flashMessage' +import PermissionsProvider from './ui/providers/PermissionsProvider' +import UserProvider from './ui/providers/UserProvider' +import PageTabs from './ui/Tabs' + +export const BASE_ROUTE = '/competitions/v2' + +const routes = [ + { + path: BASE_ROUTE, + element: ( + + + + + + + ), + children: [ + { + path: `${BASE_ROUTE}/:competition_id`, + element: ( + + + + + + + + + ), + children: [ + { + path: `${BASE_ROUTE}/:competition_id`, + element: , + }, + { + path: `${BASE_ROUTE}/:competition_id/events`, + element: , + }, + { + path: `${BASE_ROUTE}/:competition_id/schedule`, + element: , + }, + { + path: `${BASE_ROUTE}/:competition_id/register`, + element: , + }, + { + path: `${BASE_ROUTE}/:competition_id/tabs/:tab_id`, + element: , + }, + { + path: `${BASE_ROUTE}/:competition_id/registrations`, + element: , + }, + { + path: `${BASE_ROUTE}/:competition_id/:user_id/edit`, + element: , + }, + { + path: `${BASE_ROUTE}/:competition_id/registrations/edit`, + element: , + }, + ], + }, + ], + }, +] + +export default routes diff --git a/Frontend/src/ui/Competition.jsx b/Frontend/src/ui/Competition.jsx index fa1f52b0..cac87df5 100644 --- a/Frontend/src/ui/Competition.jsx +++ b/Frontend/src/ui/Competition.jsx @@ -8,6 +8,7 @@ import { useNavigate, useParams } from 'react-router-dom' import { Button, Image } from 'semantic-ui-react' import getCompetitionInfo from '../api/competition/get/get_competition_info' import { CompetitionContext } from '../api/helper/context/competition_context' +import { BASE_ROUTE } from '../routes' import styles from './competition.module.scss' import LoadingMessage from './messages/loadingMessage' @@ -64,7 +65,7 @@ export default function Competition({ children }) { onClick={(_, data) => { if (!data.disabled) { if (competitionInfo.use_wca_registration) { - navigate(`/competitions/${competitionInfo.id}/register`) + navigate(`${BASE_ROUTE}/${competitionInfo.id}/register`) } else { window.location = competitionInfo.external_registration_page diff --git a/Frontend/src/ui/Tabs.jsx b/Frontend/src/ui/Tabs.jsx index 217fea3a..acefb695 100644 --- a/Frontend/src/ui/Tabs.jsx +++ b/Frontend/src/ui/Tabs.jsx @@ -4,6 +4,7 @@ import { useLocation, useNavigate } from 'react-router-dom' import { Menu, Tab } from 'semantic-ui-react' import { CompetitionContext } from '../api/helper/context/competition_context' import { PermissionsContext } from '../api/helper/context/permission_context' +import { BASE_ROUTE } from '../routes' import styles from './tabs.module.scss' function pathMatch(name, pathname) { @@ -50,7 +51,7 @@ export default function PageTabs() { name="register" className={styles.tabItem} onClick={() => - navigate(`/competitions/${competitionInfo.id}/register`) + navigate(`${BASE_ROUTE}/${competitionInfo.id}/register`) } > @@ -68,7 +69,7 @@ export default function PageTabs() { name="registrations" className={styles.tabItem} onClick={() => - navigate(`/competitions/${competitionInfo.id}/registrations/edit`) + navigate(`${BASE_ROUTE}/${competitionInfo.id}/registrations/edit`) } > @@ -86,7 +87,7 @@ export default function PageTabs() { name="competitors" className={styles.tabItem} onClick={() => - navigate(`/competitions/${competitionInfo.id}/registrations`) + navigate(`${BASE_ROUTE}/${competitionInfo.id}/registrations`) } > @@ -103,7 +104,7 @@ export default function PageTabs() { key="tab-info" name="info" className={styles.tabItem} - onClick={() => navigate(`/competitions/${competitionInfo.id}`)} + onClick={() => navigate(`${BASE_ROUTE}/${competitionInfo.id}`)} > General Info @@ -119,7 +120,7 @@ export default function PageTabs() { name="events" className={styles.tabItem} onClick={() => - navigate(`/competitions/${competitionInfo.id}/events`) + navigate(`${BASE_ROUTE}/${competitionInfo.id}/events`) } > @@ -135,7 +136,7 @@ export default function PageTabs() { name="schedule" className={styles.tabItem} onClick={() => - navigate(`/competitions/${competitionInfo.id}/schedule`) + navigate(`${BASE_ROUTE}/${competitionInfo.id}/schedule`) } > @@ -152,7 +153,7 @@ export default function PageTabs() { name={`tabs-${tab.id}`} className={styles.tabItem} onClick={() => - navigate(`/competitions/${competitionInfo.id}/tabs/${tab.id}`) + navigate(`${BASE_ROUTE}/${competitionInfo.id}/tabs/${tab.id}`) } > {tab.name} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0876dd76..a8557fd7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,7 +12,7 @@ def validate_token token = request.headers['Authorization'].split[1] begin decoded_token = (JWT.decode token, JwtOptions.secret, true, { algorithm: JwtOptions.algorithm })[0] - @current_user = decoded_token['data']['user_id'] + @current_user = decoded_token['user_id'] rescue JWT::VerificationError, JWT::InvalidJtiError Metrics.jwt_verification_error_counter.increment render json: { error: ErrorCodes::INVALID_TOKEN }, status: :unauthorized diff --git a/app/controllers/registration_controller.rb b/app/controllers/registration_controller.rb index 955f184a..ccec2d8f 100644 --- a/app/controllers/registration_controller.rb +++ b/app/controllers/registration_controller.rb @@ -64,8 +64,8 @@ def validate_create_request user_can_create_registration! - can_compete, reasons = UserApi.can_compete?(@user_id) - raise RegistrationError.new(:unauthorized, reasons) unless can_compete + can_compete = UserApi.can_compete?(@user_id) + raise RegistrationError.new(:unauthorized, ErrorCodes::USER_PROFILE_INCOMPLETE) unless can_compete validate_events! raise RegistrationError.new(:unprocessable_entity, ErrorCodes::GUEST_LIMIT_EXCEEDED) if params.key?(:guests) && @competition.guest_limit_exceeded?(params[:guests]) @@ -171,12 +171,12 @@ def payment_ticket refresh = params[:refresh] if refresh || @registration.payment_ticket.nil? amount, currency_code = @competition.payment_info - ticket, account_id = PaymentApi.get_ticket(@registration[:attendee_id], amount, currency_code) + ticket = PaymentApi.get_ticket(@registration[:attendee_id], amount, currency_code) @registration.init_payment_lane(amount, currency_code, ticket) else ticket = @registration.payment_ticket end - render json: { client_secret_id: ticket, connected_account_id: account_id } + render json: { id: ticket } end def validate_payment_ticket_request diff --git a/app/helpers/lane_factory.rb b/app/helpers/lane_factory.rb index d71b3cb0..603ba91a 100644 --- a/app/helpers/lane_factory.rb +++ b/app/helpers/lane_factory.rb @@ -14,14 +14,14 @@ def self.competing_lane(event_ids = [], comment = '', guests = 0) competing_lane end - def self.payment_lane(fee_lowest_denominator, currency_code, payment_intent_client_secret) + def self.payment_lane(fee_lowest_denominator, currency_code, payment_id) payment_lane = Lane.new({}) payment_lane.lane_name = 'payment' payment_lane.completed_steps = ['Payment Intent Init'] payment_lane.lane_state = 'initialized' payment_lane.lane_details = { amount_lowest_denominator: fee_lowest_denominator, - payment_intent_client_secret: payment_intent_client_secret, + payment_intent_client_secret: payment_id, currency_code: currency_code, } payment_lane diff --git a/app/helpers/payment_api.rb b/app/helpers/payment_api.rb index b2550552..d550d84c 100644 --- a/app/helpers/payment_api.rb +++ b/app/helpers/payment_api.rb @@ -14,9 +14,9 @@ def self.get_ticket(attendee_id, amount, currency_code) response['id'] end - private - + class << self def payment_init_path "#{WCA_HOST}/api/internal/v1/payment/init" end + end end diff --git a/app/helpers/user_api.rb b/app/helpers/user_api.rb index 2c4102c6..54cad82e 100644 --- a/app/helpers/user_api.rb +++ b/app/helpers/user_api.rb @@ -5,6 +5,10 @@ require_relative 'mocks' require_relative 'wca_api' +def permissions_path(user_id) + "https://#{EnvConfig.WCA_HOST}/api/internal/v1/users/#{user_id}/permissions" +end + class UserApi < WcaApi def self.get_permissions(user_id) if Rails.env.production? @@ -20,7 +24,7 @@ def self.can_compete?(user_id) permissions = Rails.cache.fetch("#{user_id}-permissions", expires_in: 5.minutes) do self.get_permissions(user_id) end - [permissions['can_attend_competitions']['scope'] == '*', permissions['can_attend_competitions']['reasons']] + permissions['can_attend_competitions']['scope'] == '*' end def self.can_administer?(user_id, competition_id) @@ -29,10 +33,4 @@ def self.can_administer?(user_id, competition_id) end permissions['can_administer_competitions']['scope'] == '*' || permissions['can_administer_competitions']['scope'].include?(competition_id) end - - private - - def permissions_path(user_id) - "#{WCA_HOST}/api/internal/v1/users/#{user_id}/permissions" - end end diff --git a/app/helpers/wca_api.rb b/app/helpers/wca_api.rb index 90346edf..4ef59493 100644 --- a/app/helpers/wca_api.rb +++ b/app/helpers/wca_api.rb @@ -5,9 +5,9 @@ class WcaApi # Uses Vault ID Tokens: see https://developer.hashicorp.com/vault/docs/secrets/identity/identity-token def self.get_wca_token Vault.with_retries(Vault::HTTPConnectionError) do - data = Vault.logical.read("identity/oidc/token/#{@vault_application}") + data = Vault.logical.read("identity/oidc/token/#{EnvConfig.VAULT_APPLICATION}") if data.present? - data.data[:data][:token] + data.data[:token] else # TODO: should we hard error out here? puts 'Tried to get identity token, but got error' end diff --git a/app/models/registration.rb b/app/models/registration.rb index d78cfd61..3a5fb8e8 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -56,7 +56,7 @@ def admin_comment end def payment_ticket - lanes.filter_map { |x| x.lane_details['payment_intent_client_secret'] if x.lane_name == 'payment' }[0] + lanes.filter_map { |x| x.lane_details['payment_id'] if x.lane_name == 'payment' }[0] end def update_competing_lane!(update_params) diff --git a/infra/staging/main.tf b/infra/staging/main.tf index 941737f4..4d27aa8b 100644 --- a/infra/staging/main.tf +++ b/infra/staging/main.tf @@ -12,6 +12,10 @@ locals { name = "AWS_REGION" value = var.region }, + { + name = "WCA_HOST" + value = var.wca_host + }, { name = "DYNAMO_REGISTRATIONS_TABLE", value = aws_dynamodb_table.registrations.name diff --git a/spec/factories.rb b/spec/factories.rb index e6209c92..a3551674 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -7,7 +7,7 @@ def fetch_jwt_token(user_id) iat = Time.now.to_i jti_raw = [JwtOptions.secret, iat].join(':').to_s jti = Digest::MD5.hexdigest(jti_raw) - payload = { data: { user_id: user_id }, exp: Time.now.to_i + JwtOptions.expiry, sub: user_id, iat: iat, jti: jti } + payload = { user_id: user_id, exp: Time.now.to_i + JwtOptions.expiry, sub: user_id, iat: iat, jti: jti } token = JWT.encode payload, JwtOptions.secret, JwtOptions.algorithm "Bearer #{token}" end diff --git a/spec/requests/post_attendee_spec.rb b/spec/requests/post_attendee_spec.rb index 4478cb3a..dba279df 100644 --- a/spec/requests/post_attendee_spec.rb +++ b/spec/requests/post_attendee_spec.rb @@ -101,7 +101,7 @@ end response '401', '-> PASSING attendee is banned' do - registration_error_json = { error: ErrorCodes::USER_IS_BANNED }.to_json + registration_error_json = { error: ErrorCodes::USER_PROFILE_INCOMPLETE }.to_json let(:registration) { @banned_user_reg } let(:Authorization) { @banned_user_jwt } @@ -184,7 +184,7 @@ end response '401', '-> PASSING admin adds banned user' do - registration_error_json = { error: ErrorCodes::USER_IS_BANNED }.to_json + registration_error_json = { error: ErrorCodes::USER_PROFILE_INCOMPLETE }.to_json let(:registration) { @banned_user_reg } let(:Authorization) { @admin_token } diff --git a/spec/support/registration_spec_helper.rb b/spec/support/registration_spec_helper.rb index 8669e01d..7590d1a5 100644 --- a/spec/support/registration_spec_helper.rb +++ b/spec/support/registration_spec_helper.rb @@ -244,7 +244,7 @@ def fetch_jwt_token(user_id) iat = Time.now.to_i jti_raw = [JwtOptions.secret, iat].join(':').to_s jti = Digest::MD5.hexdigest(jti_raw) - payload = { data: { user_id: user_id }, exp: Time.now.to_i + JwtOptions.expiry, sub: user_id, iat: iat, jti: jti } + payload = { user_id: user_id, exp: Time.now.to_i + JwtOptions.expiry, sub: user_id, iat: iat, jti: jti } token = JWT.encode payload, JwtOptions.secret, JwtOptions.algorithm "Bearer #{token}" end