main
rb 73 lines 3.04 KB
Raw
1 # frozen_string_literal: true
2
3 module Api
4 # Base for all JSON API controllers.
5 #
6 # The API is token-based: clients (the siGit Code & Deploy desktop app) send
7 # the smbCloud access token as `Authorization: Bearer <token>`. Response bodies
8 # match the desktop's shared-model Rust types (smbcloud-model) exactly, so the
9 # app deserializes them directly:
10 #
11 # AccountStatus → "NotFound" | {"Ready":{"access_token":…}} | {"Incomplete":{"status":<u32>}}
12 # User → {"id":,"email":,"created_at":,"updated_at":}
13 # SignupResult → {"code":,"message":,"data":{…}}
14 # ErrorResponse → {"error_code":<i32>,"message":}
15 class BaseController < ActionController::API
16 # error_codes::ErrorCode (i32) — used in ErrorResponse.
17 ERR_UNKNOWN = 0
18 ERR_UNAUTHORIZED = 100
19 ERR_INVALID = 101
20 # account::ErrorCode (u32) — used in AccountStatus::Incomplete.status.
21 ACCOUNT_ERROR_CODES = [ 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007 ].freeze
22 ACCOUNT_EMAIL_UNVERIFIED = 1001
23
24 rescue_from SmbcloudAuthService::AuthenticationError, with: :render_auth_error
25 rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
26 rescue_from KeyError, with: :render_config_error
27
28 private
29
30 # Verifies the bearer token against smbCloud, exposes the profile in
31 # @me_profile, and upserts the local mirror. On failure halts with an
32 # ErrorResponse (401).
33 def authenticate_token!
34 token = bearer_token
35 return render_error(ERR_UNAUTHORIZED, "Missing access token.", status: :unauthorized) if token.blank?
36
37 @me_profile = SmbcloudAuthService.me(access_token: token)
38 @access_token = token
39 @current_user = User.find_or_create_from_smbcloud(@me_profile, access_token: token)
40 rescue SmbcloudAuthService::AuthenticationError
41 render_error(ERR_UNAUTHORIZED, "Invalid or expired access token.", status: :unauthorized)
42 end
43
44 def bearer_token
45 request.authorization.to_s[/\ABearer\s+(.+)\z/i, 1]&.strip
46 end
47
48 def current_user
49 @current_user
50 end
51
52 # ErrorResponse::Error — { error_code: <i32>, message: }
53 def render_error(error_code, message, status:)
54 render json: { error_code: error_code, message: message }, status: status
55 end
56
57 def render_auth_error(error)
58 # The gem's error_code is already an error_codes::ErrorCode (i32); pass it
59 # through so e.g. network errors surface as NetworkError, not Unauthorized.
60 code = error.error_code.is_a?(Integer) ? error.error_code : ERR_UNAUTHORIZED
61 render_error(code, error.message.presence || "Unauthorized.", status: :unauthorized)
62 end
63
64 def render_record_invalid(error)
65 render_error(ERR_INVALID, error.record.errors.full_messages.to_sentence, status: :unprocessable_entity)
66 end
67
68 def render_config_error(error)
69 Rails.logger.error("smbCloud configuration error: #{error.message}")
70 render_error(ERR_UNKNOWN, "Authentication service is not configured.", status: :internal_server_error)
71 end
72 end
73 end