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

Commit

Permalink
Add Vault Identity Tokens to authenticate to the monolith (#247)
Browse files Browse the repository at this point in the history
* Switched Token Generation to Vault's identity tokens

* Run rubocop

* remove test rote

* remove typo

* refactored to fit new standard

* fix rubocop
  • Loading branch information
FinnIckler authored Oct 12, 2023
1 parent 405ce56 commit eced6e0
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 38 deletions.
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ AllCops:
- 'Frontend/**/*'
- 'bin/**/*'
- 'vendor/**/*'
- 'infra/**/*'

Bundler/OrderedGems:
Enabled: false
Expand Down
68 changes: 46 additions & 22 deletions Frontend/src/api/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,33 @@

export interface paths {
"/api/v1/registrations/{competition_id}": {
/** List registrations for a given competition_id */
/** Public: list registrations for a given competition_id */
get: {
parameters: {
path: {
competition_id: string;
};
};
responses: {
/** @description Valid competition_id but no registrations for it */
/** @description PASSING comp service down but registrations exist */
200: {
content: {
"application/json": components["schemas"]["registration"][];
};
};
/** @description Competition ID doesnt exist */
/** @description PASSING Competition ID doesnt exist */
404: {
content: {
"application/json": components["schemas"]["error_response"];
};
};
/** @description Competition service unavailable - 500 error */
/** @description PASSING Competition service unavailable - 500 error */
500: {
content: {
"application/json": components["schemas"]["error_response"];
};
};
/** @description Competition service unavailable - 502 error */
/** @description PASSING Competition service unavailable - 502 error */
502: {
content: {
"application/json": components["schemas"]["error_response"];
Expand All @@ -41,31 +41,52 @@ export interface paths {
};
};
};
"/api/v1/register": {
/** Add an attendee registration */
post: {
"/api/v1/registrations/{competition_id}/admin": {
/** Public: list registrations for a given competition_id */
get: {
parameters: {
header?: {
Authorization?: string;
path: {
competition_id: string;
};
};
responses: {
/** @description PASSING organizer has access to comp 2 */
200: {
content: {
"application/json": components["schemas"]["registrationAdmin"][];
};
};
/** @description PASSING organizer cannot access registrations for comps they arent organizing - multi comp auth */
401: {
content: never;
};
};
};
};
"/api/v1/register": {
/** Add an attendee registration */
post: {
requestBody: {
content: {
"application/json": components["schemas"]["submitRegistrationBody"];
"application/json": components["schemas"]["registration"];
};
};
responses: {
/** @description only required fields included */
/** @description PASSING only required fields included */
202: {
content: {
"application/json": components["schemas"]["success_response"];
};
content: never;
};
/** @description PASSING empty payload provided */
400: {
content: never;
};
/** @description PASSING user impersonation (no admin permission, JWWT token user_id does not match registration user_id) */
401: {
content: never;
};
/** @description user impersonation attempt */
/** @description PASSING comp not open */
403: {
content: {
"application/json": components["schemas"]["error_response"];
};
content: never;
};
};
};
Expand All @@ -90,9 +111,10 @@ export interface components {
registrationAdmin: {
user_id: string;
event_ids: EventId[];
comment?: string;
admin_comment?: string;
guests?: number;
comment?: string | null;
admin_comment?: string | null;
guests?: number | null;
email?: string;
};
submitRegistrationBody: {
user_id: string;
Expand All @@ -118,6 +140,8 @@ export interface components {
pathItems: never;
}

export type $defs = Record<string, never>;

export type external = Record<string, never>;

export type operations = Record<string, never>;
14 changes: 9 additions & 5 deletions app/helpers/payment_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

require_relative 'error_codes'
require_relative 'wca_api'
require_relative 'mocks'
class PaymentApi < WcaApi
def self.get_ticket(attendee_id, amount, currency_code)
token = self.get_wca_token("payments.worldcubeassociation.org")
response = HTTParty.post("https://test-registration.worldcubeassociation.org/api/v10/payment/init",
response = HTTParty.post(payment_init_path,
body: { "attendee_id" => attendee_id, "amount" => amount, "currency_code" => currency_code }.to_json,
headers: { 'Authorization' => "Bearer: #{token}",
headers: { WCA_API_HEADER => self.get_wca_token,
"Content-Type" => "application/json" })
unless response.ok?
raise "Error from the payments service"
end
[response["client_secret"], response["connected_account_id"]]
response["id"]
end

private

def payment_init_path
"#{WCA_HOST}/api/internal/v1/payment/init"
end
end
9 changes: 7 additions & 2 deletions app/helpers/user_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
class UserApi < WcaApi
def self.get_permissions(user_id)
if Rails.env.production?
token = self.get_wca_token("users.worldcubeassociation.org")
HTTParty.get("https://test-registration.worldcubeassociation.org/api/v10/internal/users/#{user_id}/permissions", headers: { 'Authorization' => "Bearer: #{token}" })
HTTParty.get(permissions_path(user_id), headers: { WCA_API_HEADER => self.get_wca_token })
else
Mocks.permissions_mock(user_id)
end
Expand All @@ -30,4 +29,10 @@ 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
18 changes: 11 additions & 7 deletions app/helpers/wca_api.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# frozen_string_literal: true

class WcaApi
# TODO: switch this to Vault identity tokens https://developer.hashicorp.com/vault/docs/secrets/identity/identity-token
def self.get_wca_token(audience)
iat = Time.now.to_i
jti_raw = [JwtOptions.secret, iat].join(':').to_s
jti = Digest::MD5.hexdigest(jti_raw)
payload = { data: { service_id: "registration.worldcubeassociation.org" }, aud: audience, exp: Time.now.to_i + JwtOptions.expiry, sub: "registration.worldcubeassociation.org", iat: iat, jti: jti }
JWT.encode payload, JwtOptions.secret, JwtOptions.algorithm
WCA_API_HEADER = 'X-WCA-Service-Token'
# 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}")
if data.present?
data.data[:data][:token]
else # TODO: should we hard error out here?
puts "Tried to get identity token, but got error"
end
end
end
end
4 changes: 2 additions & 2 deletions infra/handler/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ variable "host" {

variable "wca_host" {
type = string
description = "The host for generating absolute URLs in the application"
default = "worldcubeassociation.org"
description = "The host for generating URLs to the monolith"
default = "https://worldcubeassociation.org"
}

variable "shared_resources" {
Expand Down

0 comments on commit eced6e0

Please sign in to comment.