From 1fc43214d4267a75e2fc121234925e523ae66768 Mon Sep 17 00:00:00 2001 From: piny940 <83708535+piny940@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:21:49 +0900 Subject: [PATCH 1/3] =?UTF-8?q?jwks=E3=82=92=E5=8F=96=E5=BE=97=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=A8=E3=83=B3=E3=83=89=E3=83=9D=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/schema/@typespec/openapi3/openapi.yaml | 12 ++++++++++++ spec/specs/oauth.tsp | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/spec/schema/@typespec/openapi3/openapi.yaml b/spec/schema/@typespec/openapi3/openapi.yaml index f6d69be..8e349cf 100644 --- a/spec/schema/@typespec/openapi3/openapi.yaml +++ b/spec/schema/@typespec/openapi3/openapi.yaml @@ -327,6 +327,18 @@ paths: multipart/form-data: schema: $ref: '#/components/schemas/OAuth.AuthorizeReqMultiPart' + /oauth/jwks: + get: + operationId: OAuthInterface_getJwks + summary: Get JSON Web Key Set + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: string /oauth/token: post: operationId: OAuthInterface_getToken diff --git a/spec/specs/oauth.tsp b/spec/specs/oauth.tsp index 346a13e..fee16ef 100644 --- a/spec/specs/oauth.tsp +++ b/spec/specs/oauth.tsp @@ -109,4 +109,12 @@ interface OAuthInterface { error: TokenErr; error_description: string; }; + + @route("/jwks") + @get + @summary("Get JSON Web Key Set") + getJwks(): { + @statusCode statusCode: 200; + @body body: string; + }; } From 7362b06a8d3dd7e66f0e8ac3a2b079b3dc95fa8a Mon Sep 17 00:00:00 2001 From: piny940 <83708535+piny940@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:21:57 +0900 Subject: [PATCH 2/3] =?UTF-8?q?IssueJwks=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.sample | 13 + frontend/src/utils/api.d.ts | 1534 ++++++++++--------- go.mod | 13 +- go.sum | 21 + internal/api/gen.go | 120 +- internal/api/oauth.go | 4 + internal/domain/oauth/init.go | 2 + internal/domain/oauth/jwks.go | 42 + internal/domain/oauth/jwks_test.go | 26 + internal/domain/oauth/token_service_test.go | 1 + 10 files changed, 988 insertions(+), 788 deletions(-) create mode 100644 internal/domain/oauth/jwks.go create mode 100644 internal/domain/oauth/jwks_test.go diff --git a/.env.sample b/.env.sample index b38dced..291648f 100644 --- a/.env.sample +++ b/.env.sample @@ -57,6 +57,19 @@ qSgIaQDEqard/iXaTHGUF88j40sU6HXtSbFYSyNshpSv0yDyWXi/UUa1SRA04tEf -----END RSA PRIVATE KEY----- " OAUTH_RSA_PRIVATE_KEY_PASSPHRASE=password +OAUTH_RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- +MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAqSqLJDQio/mpbWeBnCCm +VrW+3kvreuF26ngwDr2b/PvDsYlgk+7oICgiHAyGrh87glVXuI7QZE/8/A7f9owj +yFF9gS+ev/NdFKk72SfctqFfts+E78nnujnGtariV3YZds6LRnYi1op/mySzrmnV +cHUCUoLpbsYTisCdjfdL4tSvJ5kfs4rebiFCHxoJ2c00KCgb4Y+cPFEy4ncoxD6s +swWPu/rn2tyj7zjYoUqvsYScMyzYxouS3HateN+dBYPYX7zKGUk5PVL4yySfkutv +eEikRU0WmMyje9g33GLnSMgBPtXbkXOxjkN5AS5nvnZ023gLf8urlgQlzuRa8oLI +oGfbmR5J1YkYT0AsNYPy/JAzsFQlc23GQrTv4ay3PU23jmtgcHRF97hdV570ZMBn +AkQbTuNJ7ogkg/NarUvd/nZ9HaqhQikwexjSFoDahorpYW3kNbfA69V+f3JzRtK/ +V2PF+Zp4YJwd/dAu0kUnMNHzVMjqO4nG29+B61MTvp/nAgMBAAE= +-----END PUBLIC KEY----- +" +OAUTH_RSA_KEY_ID=qf7CErL9vRujPENk5 OAUTH_ISSUER=https://example.com SESSION_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/frontend/src/utils/api.d.ts b/frontend/src/utils/api.d.ts index 3097288..ade155b 100644 --- a/frontend/src/utils/api.d.ts +++ b/frontend/src/utils/api.d.ts @@ -4,762 +4,788 @@ */ export interface paths { - '/account/approvals': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - /** Approve a auth request */ - post: operations['ApprovalsInterface_approve'] - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/account/clients': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Get all clients */ - get: operations['AccountClients_listClients'] - put?: never - /** Create a new client */ - post: operations['AccountClients_createClient'] - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/account/clients/{id}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Get a client */ - get: operations['AccountClients_getClient'] - put?: never - /** Update a client */ - post: operations['AccountClients_updateClient'] - /** Delete a client */ - delete: operations['AccountClients_deleteClient'] - options?: never - head?: never - patch?: never - trace?: never - } - '/clients/{id}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Get a client */ - get: operations['ClientsInterface_getClient'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/healthz': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Check health */ - get: operations['Healthz_check'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/oauth/authorize': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Authorization Request */ - get: operations['OAuthInterface_authorize'] - put?: never - /** Authorization Request */ - post: operations['OAuthInterface_postAuthorize'] - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/oauth/token': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - /** Get token */ - post: operations['OAuthInterface_getToken'] - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/session': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Get session */ - get: operations['SessionInterface_me'] - put?: never - /** Login */ - post: operations['SessionInterface_login'] - /** Logout */ - delete: operations['SessionInterface_logout'] - options?: never - head?: never - patch?: never - trace?: never - } - '/users/signup': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - /** Signup */ - post: operations['UsersInterface_signup'] - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } + "/account/approvals": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Approve a auth request */ + post: operations["ApprovalsInterface_approve"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/account/clients": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get all clients */ + get: operations["AccountClients_listClients"]; + put?: never; + /** Create a new client */ + post: operations["AccountClients_createClient"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/account/clients/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get a client */ + get: operations["AccountClients_getClient"]; + put?: never; + /** Update a client */ + post: operations["AccountClients_updateClient"]; + /** Delete a client */ + delete: operations["AccountClients_deleteClient"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/clients/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get a client */ + get: operations["ClientsInterface_getClient"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/healthz": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Check health */ + get: operations["Healthz_check"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/oauth/authorize": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Authorization Request */ + get: operations["OAuthInterface_authorize"]; + put?: never; + /** Authorization Request */ + post: operations["OAuthInterface_postAuthorize"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/oauth/jwks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get JSON Web Key Set */ + get: operations["OAuthInterface_getJwks"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/oauth/token": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Get token */ + post: operations["OAuthInterface_getToken"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/session": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get session */ + get: operations["SessionInterface_me"]; + put?: never; + /** Login */ + post: operations["SessionInterface_login"]; + /** Logout */ + delete: operations["SessionInterface_logout"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/signup": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Signup */ + post: operations["UsersInterface_signup"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } -export type webhooks = Record +export type webhooks = Record; export interface components { - schemas: { - 'AccountClients.CreateClientReq': { - name: string - redirect_urls: string[] - } - 'AccountClients.CreatedClient': { - id: string - name: string - secret: string - redirect_urls: string[] - } - 'AccountClients.UpdateClientReq': { - name: string - redirect_urls: string[] - } - /** @enum {string} */ - 'Approvals.ApproveErr': 'invalid_client' | 'invalid_scope' - 'Approvals.ApproveReq': { - client_id: string - scope: string - } - Client: { - id: string - name: string - redirect_urls: string[] - } - /** @enum {string} */ - 'Clients.GetClientErr': 'client_not_found' - 'Clients.GetClientRes': { - client: components['schemas']['PublicClient'] - } - /** @enum {string} */ - 'OAuth.AuthorizeErr': - | 'invalid_request' - | 'unauthorized_client' - | 'access_denied' - | 'unsupported_response_type' - | 'invalid_scope' - | 'server_error' - | 'temporarily_unavailable' - 'OAuth.AuthorizeReqMultiPart': { - response_type: string - client_id: string - redirect_uri: string - scope: string - state?: string - } - /** @enum {string} */ - 'OAuth.TokenCacheControlHeader': 'no-store' - /** @enum {string} */ - 'OAuth.TokenErr': 'invalid_request' - /** @enum {string} */ - 'OAuth.TokenPragmaHeader': 'no-store' - 'OAuth.TokenReq': { - grant_type: string - code: string - redirect_uri: string - client_id: string - } - 'OAuth.TokenRes': { - access_token: string - token_type: components['schemas']['OAuth.TokenTokenType'] - /** Format: int32 */ - expires_in: number - /** Id token, if scope includes 'openid' */ - id_token?: string - } - /** @enum {string} */ - 'OAuth.TokenTokenType': 'Bearer' - PublicClient: { - id: string - name: string - } - /** @enum {string} */ - 'Session.LoginErr': 'invalid_name_or_password' - 'Session.LoginReq': { - name: string - password: string - } - /** @enum {string} */ - 'Session.LogoutErr': 'not_logged_in' - 'Session.MeRes': { - user: components['schemas']['User'] | null - } - User: { - /** Format: int64 */ - id: number - name: string - } - 'Users.ReqSignup': { - name: string - password: string - password_confirmation: string - } - /** @enum {string} */ - 'Users.SignupErr': - | 'name_length_not_enough' - | 'name_already_used' - | 'password_length_not_enough' - | 'password_confirmation_not_match' - } - responses: never - parameters: never - requestBodies: never - headers: never - pathItems: never + schemas: { + "AccountClients.CreateClientReq": { + name: string; + redirect_urls: string[]; + }; + "AccountClients.CreatedClient": { + id: string; + name: string; + secret: string; + redirect_urls: string[]; + }; + "AccountClients.UpdateClientReq": { + name: string; + redirect_urls: string[]; + }; + /** @enum {string} */ + "Approvals.ApproveErr": "invalid_client" | "invalid_scope"; + "Approvals.ApproveReq": { + client_id: string; + scope: string; + }; + Client: { + id: string; + name: string; + redirect_urls: string[]; + }; + /** @enum {string} */ + "Clients.GetClientErr": "client_not_found"; + "Clients.GetClientRes": { + client: components["schemas"]["PublicClient"]; + }; + /** @enum {string} */ + "OAuth.AuthorizeErr": "invalid_request" | "unauthorized_client" | "access_denied" | "unsupported_response_type" | "invalid_scope" | "server_error" | "temporarily_unavailable"; + "OAuth.AuthorizeReqMultiPart": { + response_type: string; + client_id: string; + redirect_uri: string; + scope: string; + state?: string; + }; + /** @enum {string} */ + "OAuth.TokenCacheControlHeader": "no-store"; + /** @enum {string} */ + "OAuth.TokenErr": "invalid_request"; + /** @enum {string} */ + "OAuth.TokenPragmaHeader": "no-store"; + "OAuth.TokenReq": { + grant_type: string; + code: string; + redirect_uri: string; + client_id: string; + }; + "OAuth.TokenRes": { + access_token: string; + token_type: components["schemas"]["OAuth.TokenTokenType"]; + /** Format: int32 */ + expires_in: number; + /** Id token, if scope includes 'openid' */ + id_token?: string; + }; + /** @enum {string} */ + "OAuth.TokenTokenType": "Bearer"; + PublicClient: { + id: string; + name: string; + }; + /** @enum {string} */ + "Session.LoginErr": "invalid_name_or_password"; + "Session.LoginReq": { + name: string; + password: string; + }; + /** @enum {string} */ + "Session.LogoutErr": "not_logged_in"; + "Session.MeRes": { + user: components["schemas"]["User"] | null; + }; + User: { + /** Format: int64 */ + id: number; + name: string; + }; + "Users.ReqSignup": { + name: string; + password: string; + password_confirmation: string; + }; + /** @enum {string} */ + "Users.SignupErr": "name_length_not_enough" | "name_already_used" | "password_length_not_enough" | "password_confirmation_not_match"; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } -export type $defs = Record +export type $defs = Record; export interface operations { - ApprovalsInterface_approve: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['Approvals.ApproveReq'] - } - } - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: components['schemas']['Approvals.ApproveErr'] - error_description: string - } - } - } - } - } - AccountClients_listClients: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - clients: components['schemas']['Client'][] - } - } - } - } - } - AccountClients_createClient: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody: { - content: { - 'application/json': { - client: components['schemas']['AccountClients.CreateClientReq'] - } - } - } - responses: { - /** @description The request has succeeded and a new resource has been created as a result. */ - 201: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - client: components['schemas']['AccountClients.CreatedClient'] - } - } - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: string - } - } - } - } - } - AccountClients_getClient: { - parameters: { - query?: never - header?: never - path: { - id: string - } - cookie?: never - } - requestBody?: never - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - client: components['schemas']['Client'] - } - } - } - /** @description The server cannot find the requested resource. */ - 404: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: string - } - } - } - } - } - AccountClients_updateClient: { - parameters: { - query?: never - header?: never - path: { - id: string - } - cookie?: never - } - requestBody: { - content: { - 'application/json': { - client: components['schemas']['AccountClients.UpdateClientReq'] - } - } - } - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: string - } - } - } - } - } - AccountClients_deleteClient: { - parameters: { - query?: never - header?: never - path: { - id: string - } - cookie?: never - } - requestBody?: never - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: string - } - } - } - } - } - ClientsInterface_getClient: { - parameters: { - query?: never - header?: never - path: { - id: string - } - cookie?: never - } - requestBody?: never - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Clients.GetClientRes'] - } - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: components['schemas']['Clients.GetClientErr'] - error_description: string - } - } - } - } - } - Healthz_check: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } - OAuthInterface_authorize: { - parameters: { - query: { - response_type: string - client_id: string - redirect_uri: string - scope: string - state?: string - } - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Redirection */ - 302: { - headers: { - location: string - [name: string]: unknown - } - content?: never - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: components['schemas']['OAuth.AuthorizeErr'] - error_description: string - state?: string - } - } - } - } - } - OAuthInterface_postAuthorize: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody: { - content: { - 'multipart/form-data': components['schemas']['OAuth.AuthorizeReqMultiPart'] - } - } - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Redirection */ - 302: { - headers: { - location: string - [name: string]: unknown - } - content?: never - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: components['schemas']['OAuth.AuthorizeErr'] - error_description: string - state?: string - } - } - } - } - } - OAuthInterface_getToken: { - parameters: { - query?: never - header: { - authorization: string - } - path?: never - cookie?: never - } - requestBody: { - content: { - 'application/x-www-form-urlencoded': components['schemas']['OAuth.TokenReq'] - } - } - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - 'cache-control': components['schemas']['OAuth.TokenCacheControlHeader'] - pragma: components['schemas']['OAuth.TokenPragmaHeader'] - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['OAuth.TokenRes'] - } - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: components['schemas']['OAuth.TokenErr'] - error_description: string - } - } - } - } - } - SessionInterface_me: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Session.MeRes'] - } - } - } - } - SessionInterface_login: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['Session.LoginReq'] - } - } - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - 'set-cookie': string - [name: string]: unknown - } - content?: never - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: components['schemas']['Session.LoginErr'] - error_description: string - } - } - } - } - } - SessionInterface_logout: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - 'set-cookie': string - [name: string]: unknown - } - content?: never - } - } - } - UsersInterface_signup: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['Users.ReqSignup'] - } - } - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - 'set-cookie': string - [name: string]: unknown - } - content?: never - } - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': { - error: components['schemas']['Users.SignupErr'] - error_description: string - } - } - } - } - } + ApprovalsInterface_approve: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Approvals.ApproveReq"]; + }; + }; + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: components["schemas"]["Approvals.ApproveErr"]; + error_description: string; + }; + }; + }; + }; + }; + AccountClients_listClients: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + clients: components["schemas"]["Client"][]; + }; + }; + }; + }; + }; + AccountClients_createClient: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + client: components["schemas"]["AccountClients.CreateClientReq"]; + }; + }; + }; + responses: { + /** @description The request has succeeded and a new resource has been created as a result. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + client: components["schemas"]["AccountClients.CreatedClient"]; + }; + }; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + AccountClients_getClient: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + client: components["schemas"]["Client"]; + }; + }; + }; + /** @description The server cannot find the requested resource. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + AccountClients_updateClient: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + client: components["schemas"]["AccountClients.UpdateClientReq"]; + }; + }; + }; + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + AccountClients_deleteClient: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + }; + }; + }; + }; + }; + ClientsInterface_getClient: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Clients.GetClientRes"]; + }; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: components["schemas"]["Clients.GetClientErr"]; + error_description: string; + }; + }; + }; + }; + }; + Healthz_check: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + OAuthInterface_authorize: { + parameters: { + query: { + response_type: string; + client_id: string; + redirect_uri: string; + scope: string; + state?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Redirection */ + 302: { + headers: { + location: string; + [name: string]: unknown; + }; + content?: never; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: components["schemas"]["OAuth.AuthorizeErr"]; + error_description: string; + state?: string; + }; + }; + }; + }; + }; + OAuthInterface_postAuthorize: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["OAuth.AuthorizeReqMultiPart"]; + }; + }; + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Redirection */ + 302: { + headers: { + location: string; + [name: string]: unknown; + }; + content?: never; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: components["schemas"]["OAuth.AuthorizeErr"]; + error_description: string; + state?: string; + }; + }; + }; + }; + }; + OAuthInterface_getJwks: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": string; + }; + }; + }; + }; + OAuthInterface_getToken: { + parameters: { + query?: never; + header: { + authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/x-www-form-urlencoded": components["schemas"]["OAuth.TokenReq"]; + }; + }; + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + "cache-control": components["schemas"]["OAuth.TokenCacheControlHeader"]; + pragma: components["schemas"]["OAuth.TokenPragmaHeader"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OAuth.TokenRes"]; + }; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: components["schemas"]["OAuth.TokenErr"]; + error_description: string; + }; + }; + }; + }; + }; + SessionInterface_me: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Session.MeRes"]; + }; + }; + }; + }; + SessionInterface_login: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Session.LoginReq"]; + }; + }; + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + "set-cookie": string; + [name: string]: unknown; + }; + content?: never; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: components["schemas"]["Session.LoginErr"]; + error_description: string; + }; + }; + }; + }; + }; + SessionInterface_logout: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + "set-cookie": string; + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + UsersInterface_signup: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Users.ReqSignup"]; + }; + }; + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + "set-cookie": string; + [name: string]: unknown; + }; + content?: never; + }; + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: components["schemas"]["Users.SignupErr"]; + error_description: string; + }; + }; + }; + }; + }; } diff --git a/go.mod b/go.mod index d42efdf..e10e014 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,15 @@ go 1.23.2 require ( github.com/getkin/kin-openapi v0.128.0 + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/wire v0.6.0 github.com/gorilla/securecookie v1.1.2 github.com/gorilla/sessions v1.4.0 github.com/joho/godotenv v1.5.1 github.com/kelseyhightower/envconfig v1.4.0 github.com/labstack/echo/v4 v4.12.0 + github.com/labstack/gommon v0.4.2 + github.com/lestrrat-go/jwx v1.2.30 github.com/oapi-codegen/echo-middleware v1.0.2 github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 github.com/oapi-codegen/runtime v1.1.1 @@ -22,11 +25,12 @@ require ( require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/google/subcommands v1.2.0 // indirect github.com/google/uuid v1.5.0 // indirect github.com/gorilla/mux v1.8.1 // indirect @@ -38,12 +42,17 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/labstack/gommon v0.4.2 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/go.sum b/go.sum index ec875f5..7bf56e3 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= @@ -25,6 +27,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= @@ -97,6 +101,19 @@ github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+k github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA= +github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -135,6 +152,8 @@ github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -148,7 +167,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= diff --git a/internal/api/gen.go b/internal/api/gen.go index f6db963..b8fed2c 100644 --- a/internal/api/gen.go +++ b/internal/api/gen.go @@ -287,6 +287,9 @@ type ServerInterface interface { // Authorization Request // (POST /oauth/authorize) OAuthInterfacePostAuthorize(ctx echo.Context) error + // Get JSON Web Key Set + // (GET /oauth/jwks) + OAuthInterfaceGetJwks(ctx echo.Context) error // Get token // (POST /oauth/token) OAuthInterfaceGetToken(ctx echo.Context, params OAuthInterfaceGetTokenParams) error @@ -476,6 +479,15 @@ func (w *ServerInterfaceWrapper) OAuthInterfacePostAuthorize(ctx echo.Context) e return err } +// OAuthInterfaceGetJwks converts echo context to params. +func (w *ServerInterfaceWrapper) OAuthInterfaceGetJwks(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.OAuthInterfaceGetJwks(ctx) + return err +} + // OAuthInterfaceGetToken converts echo context to params. func (w *ServerInterfaceWrapper) OAuthInterfaceGetToken(ctx echo.Context) error { var err error @@ -587,6 +599,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/healthz", wrapper.HealthzCheck) router.GET(baseURL+"/oauth/authorize", wrapper.OAuthInterfaceAuthorize) router.POST(baseURL+"/oauth/authorize", wrapper.OAuthInterfacePostAuthorize) + router.GET(baseURL+"/oauth/jwks", wrapper.OAuthInterfaceGetJwks) router.POST(baseURL+"/oauth/token", wrapper.OAuthInterfaceGetToken) router.DELETE(baseURL+"/session", wrapper.SessionInterfaceLogout) router.GET(baseURL+"/session", wrapper.SessionInterfaceMe) @@ -886,6 +899,22 @@ func (response OAuthInterfacePostAuthorize400JSONResponse) VisitOAuthInterfacePo return json.NewEncoder(w).Encode(response) } +type OAuthInterfaceGetJwksRequestObject struct { +} + +type OAuthInterfaceGetJwksResponseObject interface { + VisitOAuthInterfaceGetJwksResponse(w http.ResponseWriter) error +} + +type OAuthInterfaceGetJwks200JSONResponse string + +func (response OAuthInterfaceGetJwks200JSONResponse) VisitOAuthInterfaceGetJwksResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + type OAuthInterfaceGetTokenRequestObject struct { Params OAuthInterfaceGetTokenParams Body *OAuthInterfaceGetTokenFormdataRequestBody @@ -1063,6 +1092,9 @@ type StrictServerInterface interface { // Authorization Request // (POST /oauth/authorize) OAuthInterfacePostAuthorize(ctx context.Context, request OAuthInterfacePostAuthorizeRequestObject) (OAuthInterfacePostAuthorizeResponseObject, error) + // Get JSON Web Key Set + // (GET /oauth/jwks) + OAuthInterfaceGetJwks(ctx context.Context, request OAuthInterfaceGetJwksRequestObject) (OAuthInterfaceGetJwksResponseObject, error) // Get token // (POST /oauth/token) OAuthInterfaceGetToken(ctx context.Context, request OAuthInterfaceGetTokenRequestObject) (OAuthInterfaceGetTokenResponseObject, error) @@ -1356,6 +1388,29 @@ func (sh *strictHandler) OAuthInterfacePostAuthorize(ctx echo.Context) error { return nil } +// OAuthInterfaceGetJwks operation middleware +func (sh *strictHandler) OAuthInterfaceGetJwks(ctx echo.Context) error { + var request OAuthInterfaceGetJwksRequestObject + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.OAuthInterfaceGetJwks(ctx.Request().Context(), request.(OAuthInterfaceGetJwksRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "OAuthInterfaceGetJwks") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(OAuthInterfaceGetJwksResponseObject); ok { + return validResponse.VisitOAuthInterfaceGetJwksResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + // OAuthInterfaceGetToken operation middleware func (sh *strictHandler) OAuthInterfaceGetToken(ctx echo.Context, params OAuthInterfaceGetTokenParams) error { var request OAuthInterfaceGetTokenRequestObject @@ -1498,38 +1553,39 @@ func (sh *strictHandler) UsersInterfaceSignup(ctx echo.Context) error { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "SM5Ht7/i9fX67wAAAP//g+SDdGg0AAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/api/oauth.go b/internal/api/oauth.go index 5e04f4d..ab0493d 100644 --- a/internal/api/oauth.go +++ b/internal/api/oauth.go @@ -172,3 +172,7 @@ func toDAuthParams(params OAuthInterfaceAuthorizeParams) *oauth.AuthRequest { State: params.State, } } + +func (s *Server) OAuthInterfaceGetJwks(ctx context.Context, request OAuthInterfaceGetJwksRequestObject) (OAuthInterfaceGetJwksResponseObject, error) { + panic("unimplemented") +} diff --git a/internal/domain/oauth/init.go b/internal/domain/oauth/init.go index 9185e55..c95b90a 100644 --- a/internal/domain/oauth/init.go +++ b/internal/domain/oauth/init.go @@ -5,6 +5,8 @@ import "github.com/kelseyhightower/envconfig" type Config struct { RsaPrivateKey string `required:"true" split_words:"true"` RsaPrivateKeyPassphrase string `required:"true" split_words:"true"` + RsaPublicKey string `required:"true" split_words:"true"` + RsaKeyId string `required:"true" split_words:"true"` Issuer string `required:"true"` } diff --git a/internal/domain/oauth/jwks.go b/internal/domain/oauth/jwks.go new file mode 100644 index 0000000..0648f3b --- /dev/null +++ b/internal/domain/oauth/jwks.go @@ -0,0 +1,42 @@ +package oauth + +import ( + "crypto/rsa" + "errors" + "fmt" + + "github.com/golang-jwt/jwt" + "github.com/lestrrat-go/jwx/jwk" +) + +type JWKsService struct { + rsaPublicKey *rsa.PublicKey + rsaKeyId string +} + +func NewJWKsService(conf *Config) *JWKsService { + key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(conf.RsaPublicKey)) + if err != nil { + panic(err) + } + return &JWKsService{ + rsaPublicKey: key, + rsaKeyId: conf.RsaKeyId, + } +} + +func (s *JWKsService) IssueJwks() (jwk.Set, error) { + key, err := jwk.New(s.rsaPublicKey) + if err != nil { + return nil, fmt.Errorf("failed to create JWKs: %w", err) + } + if _, ok := key.(jwk.RSAPrivateKey); ok { + return nil, fmt.Errorf("failed to create JWKs: %s", ErrInvalidKeyType) + } + key.Set(jwk.KeyIDKey, s.rsaKeyId) + jwks := jwk.NewSet() + jwks.Add(key) + return jwks, nil +} + +var ErrInvalidKeyType = errors.New("invalid key type") diff --git a/internal/domain/oauth/jwks_test.go b/internal/domain/oauth/jwks_test.go new file mode 100644 index 0000000..62f91cf --- /dev/null +++ b/internal/domain/oauth/jwks_test.go @@ -0,0 +1,26 @@ +package oauth + +import ( + "crypto/rsa" + "testing" +) + +func TestIssueJwks(t *testing.T) { + conf := &Config{ + RsaPublicKey: RSA_PUBLIC_KEY, + RsaKeyId: RSA_KEY_ID, + } + jwkSvc := NewJWKsService(conf) + jwks, err := jwkSvc.IssueJwks() + if err != nil { + t.Fatal(err) + } + jwk, ok := jwks.LookupKeyID(RSA_KEY_ID) + if !ok { + t.Errorf("JWK not found: %s", RSA_KEY_ID) + } + var raw rsa.PublicKey + if err := jwk.Raw(&raw); err != nil { + t.Fatal(err) + } +} diff --git a/internal/domain/oauth/token_service_test.go b/internal/domain/oauth/token_service_test.go index 7f7bfa0..c08d181 100644 --- a/internal/domain/oauth/token_service_test.go +++ b/internal/domain/oauth/token_service_test.go @@ -189,4 +189,5 @@ V2PF+Zp4YJwd/dAu0kUnMNHzVMjqO4nG29+B61MTvp/nAgMBAAE= -----END PUBLIC KEY----- ` RSA_PASS_PHRASE = "password" + RSA_KEY_ID = "qf7CErL9vRujPENk5" ) From 3eec2eb73d96e0988b638f75817e6aaef76faba4 Mon Sep 17 00:00:00 2001 From: piny940 <83708535+piny940@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:00:52 +0900 Subject: [PATCH 3/3] =?UTF-8?q?jwks=E3=82=92=E8=BF=94=E5=8D=B4=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=A8=E3=83=B3=E3=83=89=E3=83=9D=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/api.d.ts | 1573 ++++++++++--------- internal/api/gen.go | 68 +- internal/api/oauth.go | 15 +- internal/api/util.go | 21 + internal/di/wire.go | 1 + internal/di/wire_gen.go | 5 +- internal/usecase/oauth.go | 13 + spec/pnpm-lock.yaml | 6 +- spec/schema/@typespec/openapi3/openapi.yaml | 3 +- spec/specs/oauth.tsp | 2 +- 10 files changed, 883 insertions(+), 824 deletions(-) diff --git a/frontend/src/utils/api.d.ts b/frontend/src/utils/api.d.ts index ade155b..138af86 100644 --- a/frontend/src/utils/api.d.ts +++ b/frontend/src/utils/api.d.ts @@ -4,788 +4,801 @@ */ export interface paths { - "/account/approvals": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** Approve a auth request */ - post: operations["ApprovalsInterface_approve"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/account/clients": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get all clients */ - get: operations["AccountClients_listClients"]; - put?: never; - /** Create a new client */ - post: operations["AccountClients_createClient"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/account/clients/{id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get a client */ - get: operations["AccountClients_getClient"]; - put?: never; - /** Update a client */ - post: operations["AccountClients_updateClient"]; - /** Delete a client */ - delete: operations["AccountClients_deleteClient"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/clients/{id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get a client */ - get: operations["ClientsInterface_getClient"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/healthz": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Check health */ - get: operations["Healthz_check"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/oauth/authorize": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Authorization Request */ - get: operations["OAuthInterface_authorize"]; - put?: never; - /** Authorization Request */ - post: operations["OAuthInterface_postAuthorize"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/oauth/jwks": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get JSON Web Key Set */ - get: operations["OAuthInterface_getJwks"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/oauth/token": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** Get token */ - post: operations["OAuthInterface_getToken"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/session": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get session */ - get: operations["SessionInterface_me"]; - put?: never; - /** Login */ - post: operations["SessionInterface_login"]; - /** Logout */ - delete: operations["SessionInterface_logout"]; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/users/signup": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** Signup */ - post: operations["UsersInterface_signup"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; + '/account/approvals': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** Approve a auth request */ + post: operations['ApprovalsInterface_approve'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/account/clients': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Get all clients */ + get: operations['AccountClients_listClients'] + put?: never + /** Create a new client */ + post: operations['AccountClients_createClient'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/account/clients/{id}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Get a client */ + get: operations['AccountClients_getClient'] + put?: never + /** Update a client */ + post: operations['AccountClients_updateClient'] + /** Delete a client */ + delete: operations['AccountClients_deleteClient'] + options?: never + head?: never + patch?: never + trace?: never + } + '/clients/{id}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Get a client */ + get: operations['ClientsInterface_getClient'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/healthz': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Check health */ + get: operations['Healthz_check'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/oauth/authorize': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Authorization Request */ + get: operations['OAuthInterface_authorize'] + put?: never + /** Authorization Request */ + post: operations['OAuthInterface_postAuthorize'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/oauth/jwks': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Get JSON Web Key Set */ + get: operations['OAuthInterface_getJwks'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/oauth/token': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** Get token */ + post: operations['OAuthInterface_getToken'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/session': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Get session */ + get: operations['SessionInterface_me'] + put?: never + /** Login */ + post: operations['SessionInterface_login'] + /** Logout */ + delete: operations['SessionInterface_logout'] + options?: never + head?: never + patch?: never + trace?: never + } + '/users/signup': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** Signup */ + post: operations['UsersInterface_signup'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } } -export type webhooks = Record; +export type webhooks = Record export interface components { - schemas: { - "AccountClients.CreateClientReq": { - name: string; - redirect_urls: string[]; - }; - "AccountClients.CreatedClient": { - id: string; - name: string; - secret: string; - redirect_urls: string[]; - }; - "AccountClients.UpdateClientReq": { - name: string; - redirect_urls: string[]; - }; - /** @enum {string} */ - "Approvals.ApproveErr": "invalid_client" | "invalid_scope"; - "Approvals.ApproveReq": { - client_id: string; - scope: string; - }; - Client: { - id: string; - name: string; - redirect_urls: string[]; - }; - /** @enum {string} */ - "Clients.GetClientErr": "client_not_found"; - "Clients.GetClientRes": { - client: components["schemas"]["PublicClient"]; - }; - /** @enum {string} */ - "OAuth.AuthorizeErr": "invalid_request" | "unauthorized_client" | "access_denied" | "unsupported_response_type" | "invalid_scope" | "server_error" | "temporarily_unavailable"; - "OAuth.AuthorizeReqMultiPart": { - response_type: string; - client_id: string; - redirect_uri: string; - scope: string; - state?: string; - }; - /** @enum {string} */ - "OAuth.TokenCacheControlHeader": "no-store"; - /** @enum {string} */ - "OAuth.TokenErr": "invalid_request"; - /** @enum {string} */ - "OAuth.TokenPragmaHeader": "no-store"; - "OAuth.TokenReq": { - grant_type: string; - code: string; - redirect_uri: string; - client_id: string; - }; - "OAuth.TokenRes": { - access_token: string; - token_type: components["schemas"]["OAuth.TokenTokenType"]; - /** Format: int32 */ - expires_in: number; - /** Id token, if scope includes 'openid' */ - id_token?: string; - }; - /** @enum {string} */ - "OAuth.TokenTokenType": "Bearer"; - PublicClient: { - id: string; - name: string; - }; - /** @enum {string} */ - "Session.LoginErr": "invalid_name_or_password"; - "Session.LoginReq": { - name: string; - password: string; - }; - /** @enum {string} */ - "Session.LogoutErr": "not_logged_in"; - "Session.MeRes": { - user: components["schemas"]["User"] | null; - }; - User: { - /** Format: int64 */ - id: number; - name: string; - }; - "Users.ReqSignup": { - name: string; - password: string; - password_confirmation: string; - }; - /** @enum {string} */ - "Users.SignupErr": "name_length_not_enough" | "name_already_used" | "password_length_not_enough" | "password_confirmation_not_match"; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + schemas: { + 'AccountClients.CreateClientReq': { + name: string + redirect_urls: string[] + } + 'AccountClients.CreatedClient': { + id: string + name: string + secret: string + redirect_urls: string[] + } + 'AccountClients.UpdateClientReq': { + name: string + redirect_urls: string[] + } + /** @enum {string} */ + 'Approvals.ApproveErr': 'invalid_client' | 'invalid_scope' + 'Approvals.ApproveReq': { + client_id: string + scope: string + } + Client: { + id: string + name: string + redirect_urls: string[] + } + /** @enum {string} */ + 'Clients.GetClientErr': 'client_not_found' + 'Clients.GetClientRes': { + client: components['schemas']['PublicClient'] + } + /** @enum {string} */ + 'OAuth.AuthorizeErr': + | 'invalid_request' + | 'unauthorized_client' + | 'access_denied' + | 'unsupported_response_type' + | 'invalid_scope' + | 'server_error' + | 'temporarily_unavailable' + 'OAuth.AuthorizeReqMultiPart': { + response_type: string + client_id: string + redirect_uri: string + scope: string + state?: string + } + /** @enum {string} */ + 'OAuth.TokenCacheControlHeader': 'no-store' + /** @enum {string} */ + 'OAuth.TokenErr': 'invalid_request' + /** @enum {string} */ + 'OAuth.TokenPragmaHeader': 'no-store' + 'OAuth.TokenReq': { + grant_type: string + code: string + redirect_uri: string + client_id: string + } + 'OAuth.TokenRes': { + access_token: string + token_type: components['schemas']['OAuth.TokenTokenType'] + /** Format: int32 */ + expires_in: number + /** Id token, if scope includes 'openid' */ + id_token?: string + } + /** @enum {string} */ + 'OAuth.TokenTokenType': 'Bearer' + PublicClient: { + id: string + name: string + } + /** @enum {string} */ + 'Session.LoginErr': 'invalid_name_or_password' + 'Session.LoginReq': { + name: string + password: string + } + /** @enum {string} */ + 'Session.LogoutErr': 'not_logged_in' + 'Session.MeRes': { + user: components['schemas']['User'] | null + } + User: { + /** Format: int64 */ + id: number + name: string + } + 'Users.ReqSignup': { + name: string + password: string + password_confirmation: string + } + /** @enum {string} */ + 'Users.SignupErr': + | 'name_length_not_enough' + | 'name_already_used' + | 'password_length_not_enough' + | 'password_confirmation_not_match' + } + responses: never + parameters: never + requestBodies: never + headers: never + pathItems: never } -export type $defs = Record; +export type $defs = Record export interface operations { - ApprovalsInterface_approve: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Approvals.ApproveReq"]; - }; - }; - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: components["schemas"]["Approvals.ApproveErr"]; - error_description: string; - }; - }; - }; - }; - }; - AccountClients_listClients: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - clients: components["schemas"]["Client"][]; - }; - }; - }; - }; - }; - AccountClients_createClient: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - client: components["schemas"]["AccountClients.CreateClientReq"]; - }; - }; - }; - responses: { - /** @description The request has succeeded and a new resource has been created as a result. */ - 201: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - client: components["schemas"]["AccountClients.CreatedClient"]; - }; - }; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: string; - }; - }; - }; - }; - }; - AccountClients_getClient: { - parameters: { - query?: never; - header?: never; - path: { - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - client: components["schemas"]["Client"]; - }; - }; - }; - /** @description The server cannot find the requested resource. */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: string; - }; - }; - }; - }; - }; - AccountClients_updateClient: { - parameters: { - query?: never; - header?: never; - path: { - id: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - client: components["schemas"]["AccountClients.UpdateClientReq"]; - }; - }; - }; - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: string; - }; - }; - }; - }; - }; - AccountClients_deleteClient: { - parameters: { - query?: never; - header?: never; - path: { - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: string; - }; - }; - }; - }; - }; - ClientsInterface_getClient: { - parameters: { - query?: never; - header?: never; - path: { - id: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Clients.GetClientRes"]; - }; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: components["schemas"]["Clients.GetClientErr"]; - error_description: string; - }; - }; - }; - }; - }; - Healthz_check: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; - }; - OAuthInterface_authorize: { - parameters: { - query: { - response_type: string; - client_id: string; - redirect_uri: string; - scope: string; - state?: string; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Redirection */ - 302: { - headers: { - location: string; - [name: string]: unknown; - }; - content?: never; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: components["schemas"]["OAuth.AuthorizeErr"]; - error_description: string; - state?: string; - }; - }; - }; - }; - }; - OAuthInterface_postAuthorize: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["OAuth.AuthorizeReqMultiPart"]; - }; - }; - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Redirection */ - 302: { - headers: { - location: string; - [name: string]: unknown; - }; - content?: never; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: components["schemas"]["OAuth.AuthorizeErr"]; - error_description: string; - state?: string; - }; - }; - }; - }; - }; - OAuthInterface_getJwks: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": string; - }; - }; - }; - }; - OAuthInterface_getToken: { - parameters: { - query?: never; - header: { - authorization: string; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/x-www-form-urlencoded": components["schemas"]["OAuth.TokenReq"]; - }; - }; - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - "cache-control": components["schemas"]["OAuth.TokenCacheControlHeader"]; - pragma: components["schemas"]["OAuth.TokenPragmaHeader"]; - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["OAuth.TokenRes"]; - }; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: components["schemas"]["OAuth.TokenErr"]; - error_description: string; - }; - }; - }; - }; - }; - SessionInterface_me: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The request has succeeded. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Session.MeRes"]; - }; - }; - }; - }; - SessionInterface_login: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Session.LoginReq"]; - }; - }; - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - "set-cookie": string; - [name: string]: unknown; - }; - content?: never; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: components["schemas"]["Session.LoginErr"]; - error_description: string; - }; - }; - }; - }; - }; - SessionInterface_logout: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - "set-cookie": string; - [name: string]: unknown; - }; - content?: never; - }; - }; - }; - UsersInterface_signup: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Users.ReqSignup"]; - }; - }; - responses: { - /** @description There is no content to send for this request, but the headers may be useful. */ - 204: { - headers: { - "set-cookie": string; - [name: string]: unknown; - }; - content?: never; - }; - /** @description The server could not understand the request due to invalid syntax. */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - error: components["schemas"]["Users.SignupErr"]; - error_description: string; - }; - }; - }; - }; - }; + ApprovalsInterface_approve: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['Approvals.ApproveReq'] + } + } + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: components['schemas']['Approvals.ApproveErr'] + error_description: string + } + } + } + } + } + AccountClients_listClients: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + clients: components['schemas']['Client'][] + } + } + } + } + } + AccountClients_createClient: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/json': { + client: components['schemas']['AccountClients.CreateClientReq'] + } + } + } + responses: { + /** @description The request has succeeded and a new resource has been created as a result. */ + 201: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + client: components['schemas']['AccountClients.CreatedClient'] + } + } + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: string + } + } + } + } + } + AccountClients_getClient: { + parameters: { + query?: never + header?: never + path: { + id: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + client: components['schemas']['Client'] + } + } + } + /** @description The server cannot find the requested resource. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: string + } + } + } + } + } + AccountClients_updateClient: { + parameters: { + query?: never + header?: never + path: { + id: string + } + cookie?: never + } + requestBody: { + content: { + 'application/json': { + client: components['schemas']['AccountClients.UpdateClientReq'] + } + } + } + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: string + } + } + } + } + } + AccountClients_deleteClient: { + parameters: { + query?: never + header?: never + path: { + id: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: string + } + } + } + } + } + ClientsInterface_getClient: { + parameters: { + query?: never + header?: never + path: { + id: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['Clients.GetClientRes'] + } + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: components['schemas']['Clients.GetClientErr'] + error_description: string + } + } + } + } + } + Healthz_check: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown + } + content?: never + } + } + } + OAuthInterface_authorize: { + parameters: { + query: { + response_type: string + client_id: string + redirect_uri: string + scope: string + state?: string + } + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description Redirection */ + 302: { + headers: { + location: string + [name: string]: unknown + } + content?: never + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: components['schemas']['OAuth.AuthorizeErr'] + error_description: string + state?: string + } + } + } + } + } + OAuthInterface_postAuthorize: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody: { + content: { + 'multipart/form-data': components['schemas']['OAuth.AuthorizeReqMultiPart'] + } + } + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description Redirection */ + 302: { + headers: { + location: string + [name: string]: unknown + } + content?: never + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: components['schemas']['OAuth.AuthorizeErr'] + error_description: string + state?: string + } + } + } + } + } + OAuthInterface_getJwks: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + [key: string]: unknown + } + } + } + } + } + OAuthInterface_getToken: { + parameters: { + query?: never + header: { + authorization: string + } + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/x-www-form-urlencoded': components['schemas']['OAuth.TokenReq'] + } + } + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + 'cache-control': components['schemas']['OAuth.TokenCacheControlHeader'] + pragma: components['schemas']['OAuth.TokenPragmaHeader'] + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['OAuth.TokenRes'] + } + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: components['schemas']['OAuth.TokenErr'] + error_description: string + } + } + } + } + } + SessionInterface_me: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description The request has succeeded. */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['Session.MeRes'] + } + } + } + } + SessionInterface_login: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['Session.LoginReq'] + } + } + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + 'set-cookie': string + [name: string]: unknown + } + content?: never + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: components['schemas']['Session.LoginErr'] + error_description: string + } + } + } + } + } + SessionInterface_logout: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + 'set-cookie': string + [name: string]: unknown + } + content?: never + } + } + } + UsersInterface_signup: { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['Users.ReqSignup'] + } + } + responses: { + /** @description There is no content to send for this request, but the headers may be useful. */ + 204: { + headers: { + 'set-cookie': string + [name: string]: unknown + } + content?: never + } + /** @description The server could not understand the request due to invalid syntax. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': { + error: components['schemas']['Users.SignupErr'] + error_description: string + } + } + } + } + } } diff --git a/internal/api/gen.go b/internal/api/gen.go index b8fed2c..45677ae 100644 --- a/internal/api/gen.go +++ b/internal/api/gen.go @@ -906,7 +906,7 @@ type OAuthInterfaceGetJwksResponseObject interface { VisitOAuthInterfaceGetJwksResponse(w http.ResponseWriter) error } -type OAuthInterfaceGetJwks200JSONResponse string +type OAuthInterfaceGetJwks200JSONResponse map[string]interface{} func (response OAuthInterfaceGetJwks200JSONResponse) VisitOAuthInterfaceGetJwksResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") @@ -1553,39 +1553,39 @@ func (sh *strictHandler) UsersInterfaceSignup(ctx echo.Context) error { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "SM5Ht7/i9fX67wAAAP//g+SDdGg0AAA=", + "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", + "v8/HQ6qWI5Lz0d2veH2z/jsAAP//EyCq74I0AAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/api/oauth.go b/internal/api/oauth.go index ab0493d..dfcf5d8 100644 --- a/internal/api/oauth.go +++ b/internal/api/oauth.go @@ -5,6 +5,7 @@ import ( "auth/internal/domain/oauth" "auth/internal/usecase" "context" + "encoding/json" "errors" "net/url" "strings" @@ -174,5 +175,17 @@ func toDAuthParams(params OAuthInterfaceAuthorizeParams) *oauth.AuthRequest { } func (s *Server) OAuthInterfaceGetJwks(ctx context.Context, request OAuthInterfaceGetJwksRequestObject) (OAuthInterfaceGetJwksResponseObject, error) { - panic("unimplemented") + set, err := s.OAuthUsecase.GetJWKs() + if err != nil { + return nil, err + } + data, err := json.Marshal(set) + if err != nil { + return nil, err + } + res := make(map[string]interface{}) + if err := json.Unmarshal(data, &res); err != nil { + return nil, err + } + return OAuthInterfaceGetJwks200JSONResponse(res), nil } diff --git a/internal/api/util.go b/internal/api/util.go index d2ef78c..79dc576 100644 --- a/internal/api/util.go +++ b/internal/api/util.go @@ -6,6 +6,7 @@ import ( "errors" "net/http" "net/url" + "reflect" "strings" "github.com/gorilla/securecookie" @@ -116,3 +117,23 @@ var ( ErrUnauthorized = errors.New("unauthorized") ErrNotFoundInSession = errors.New("not found in session") ) + +func toMap(obj interface{}) map[string]interface{} { + result := make(map[string]interface{}) + value := reflect.ValueOf(obj) + + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + if value.Kind() != reflect.Struct { + return result + } + + typ := reflect.TypeOf(obj) + for i := 0; i < value.NumField(); i++ { + field := typ.Field(i) + fieldValue := value.Field(i) + result[field.Name] = fieldValue.Interface() + } + return result +} diff --git a/internal/di/wire.go b/internal/di/wire.go index 83633ee..e870fb9 100644 --- a/internal/di/wire.go +++ b/internal/di/wire.go @@ -26,6 +26,7 @@ func NewServer() *api.Server { oauth.NewAuthCodeService, oauth.NewRequestService, oauth.NewTokenService, + oauth.NewJWKsService, oauth.NewConfig, gateway.NewClientRepo, gateway.NewAuthCodeRepo, diff --git a/internal/di/wire_gen.go b/internal/di/wire_gen.go index d7d18e3..ee84d94 100644 --- a/internal/di/wire_gen.go +++ b/internal/di/wire_gen.go @@ -28,10 +28,11 @@ func NewServer() *api.Server { iAuthCodeRepo := gateway.NewAuthCodeRepo(db) requestService := oauth.NewRequestService(iClientRepo, iApprovalRepo, iAuthCodeRepo) authCodeService := oauth.NewAuthCodeService(iAuthCodeRepo) - approvalService := oauth.NewApprovalService(iApprovalRepo, iClientRepo) config := oauth.NewConfig() + jwKsService := oauth.NewJWKsService(config) + approvalService := oauth.NewApprovalService(iApprovalRepo, iClientRepo) tokenService := oauth.NewTokenService(config, iUserRepo) - oAuthUsecase := usecase.NewOAuthUsecase(requestService, authCodeService, approvalService, tokenService, iClientRepo) + oAuthUsecase := usecase.NewOAuthUsecase(requestService, authCodeService, jwKsService, approvalService, tokenService, iClientRepo) iClientUsecase := usecase.NewClientUsecase(iClientRepo) server := api.NewServer(userUsecase, oAuthUsecase, iClientUsecase) return server diff --git a/internal/usecase/oauth.go b/internal/usecase/oauth.go index 107c26e..e9880b3 100644 --- a/internal/usecase/oauth.go +++ b/internal/usecase/oauth.go @@ -6,6 +6,8 @@ import ( "errors" "fmt" "slices" + + "github.com/lestrrat-go/jwx/jwk" ) type OAuthUsecase struct { @@ -13,18 +15,21 @@ type OAuthUsecase struct { AuthCodeService *oauth.AuthCodeService ApprovalService *oauth.ApprovalService TokenService *oauth.TokenService + JWKsService *oauth.JWKsService ClientRepo oauth.IClientRepo } func NewOAuthUsecase( reqSvc *oauth.RequestService, authCodeSvc *oauth.AuthCodeService, + jwksService *oauth.JWKsService, approvalSvc *oauth.ApprovalService, tokenSvc *oauth.TokenService, clientRepo oauth.IClientRepo, ) *OAuthUsecase { return &OAuthUsecase{ RequestService: reqSvc, + JWKsService: jwksService, AuthCodeService: authCodeSvc, ApprovalService: approvalSvc, TokenService: tokenSvc, @@ -86,6 +91,14 @@ func (u *OAuthUsecase) RequestToken(req *TokenRequest) (*oauth.AccessToken, *oau } } +func (u *OAuthUsecase) GetJWKs() (jwk.Set, error) { + set, err := u.JWKsService.IssueJwks() + if err != nil { + return nil, fmt.Errorf("failed to issue jwks: %w", err) + } + return set, nil +} + var ( ErrPasswordNotMatch = errors.New("invalid password") ErrNotApproved = errors.New("not approved") diff --git a/spec/pnpm-lock.yaml b/spec/pnpm-lock.yaml index b57812d..7fa727c 100644 --- a/spec/pnpm-lock.yaml +++ b/spec/pnpm-lock.yaml @@ -2912,10 +2912,6 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.1 - debug@4.3.7: - dependencies: - ms: 2.1.3 - debug@4.3.7(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -4117,7 +4113,7 @@ snapshots: simple-websocket@9.1.0: dependencies: - debug: 4.3.7 + debug: 4.3.7(supports-color@5.5.0) queue-microtask: 1.2.3 randombytes: 2.1.0 readable-stream: 3.6.2 diff --git a/spec/schema/@typespec/openapi3/openapi.yaml b/spec/schema/@typespec/openapi3/openapi.yaml index 8e349cf..b84a013 100644 --- a/spec/schema/@typespec/openapi3/openapi.yaml +++ b/spec/schema/@typespec/openapi3/openapi.yaml @@ -338,7 +338,8 @@ paths: content: application/json: schema: - type: string + type: object + additionalProperties: {} /oauth/token: post: operationId: OAuthInterface_getToken diff --git a/spec/specs/oauth.tsp b/spec/specs/oauth.tsp index fee16ef..afa394f 100644 --- a/spec/specs/oauth.tsp +++ b/spec/specs/oauth.tsp @@ -115,6 +115,6 @@ interface OAuthInterface { @summary("Get JSON Web Key Set") getJwks(): { @statusCode statusCode: 200; - @body body: string; + @body body: Record; }; }