{"components":{"parameters":{"OAuthClientId":{"in":"query","name":"client_id","required":true,"schema":{"type":"string"}},"OAuthRedirectUri":{"in":"query","name":"redirect_uri","required":true,"schema":{"type":"string"}},"OAuthScope":{"in":"query","name":"scope","required":false,"schema":{"type":"string"}},"OAuthState":{"in":"query","name":"state","required":false,"schema":{"type":"string"}}},"responses":{"BadRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Permintaan tidak valid"},"Forbidden":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Scope tidak mencukupi atau akses ditolak"},"OAuth2Error":{"content":{"application/json":{"schema":{"properties":{"error":{"example":"invalid_grant","type":"string"},"error_description":{"type":"string"}},"type":"object"}}},"description":"OAuth2 error response"},"Unauthorized":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Token tidak valid atau tidak disertakan"}},"schemas":{"ErrorResponse":{"properties":{"error":{"type":"string"},"error_description":{"type":"string"},"message":{"type":"string"}},"type":"object"},"HealthResponse":{"properties":{"services":{"properties":{"database":{"properties":{"status":{"enum":["ok","error"],"type":"string"}},"type":"object"},"redis":{"properties":{"status":{"enum":["ok","error","disabled"],"type":"string"}},"type":"object"},"simaster":{"properties":{"status":{"enum":["ok","error","disabled"],"type":"string"}},"type":"object"}},"type":"object"},"status":{"enum":["ok","degraded","error"],"type":"string"},"timestamp":{"format":"date-time","type":"string"},"version":{"type":"string"}},"type":"object"},"IntrospectResponse":{"properties":{"active":{"description":"True jika token valid dan belum expired","type":"boolean"},"client_id":{"type":"string"},"data":{"description":"Data user (hanya untuk user-based grants)","properties":{"attributes":{"items":{"type":"object"},"type":"array"},"client_id":{"type":"string"},"email":{"type":"string"},"unit_id":{"type":"integer"},"username":{"type":"string"}},"type":"object"},"exp":{"type":"integer"},"grant_type":{"type":"string"},"iat":{"type":"integer"},"iss":{"type":"string"},"scope":{"type":"string"},"sub":{"description":"Username (untuk user-based grants)","type":"string"},"token_type":{"example":"Bearer","type":"string"}},"type":"object"},"ServerMetadata":{"description":"RFC 8414 / OIDC Discovery metadata","properties":{"authorization_endpoint":{"type":"string"},"end_session_endpoint":{"type":"string"},"grant_types_supported":{"items":{"type":"string"},"type":"array"},"id_token_signing_alg_values_supported":{"items":{"type":"string"},"type":"array"},"introspection_endpoint":{"type":"string"},"issuer":{"type":"string"},"jwks_uri":{"type":"string"},"response_types_supported":{"items":{"type":"string"},"type":"array"},"revocation_endpoint":{"type":"string"},"scopes_supported":{"items":{"type":"string"},"type":"array"},"token_endpoint":{"type":"string"},"token_endpoint_auth_methods_supported":{"items":{"type":"string"},"type":"array"},"userinfo_endpoint":{"type":"string"}},"type":"object"},"TokenResponse":{"description":"Respons token OAuth2","example":{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...","expires_in":3600,"refresh_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...","scope":"openid user.read","token_type":"Bearer"},"properties":{"access_token":{"description":"JWT access token","type":"string"},"expires_in":{"description":"Masa berlaku dalam detik","example":3600,"type":"integer"},"id_token":{"description":"OIDC ID Token (hanya jika scope openid)","type":"string"},"refresh_token":{"description":"JWT refresh token (tidak selalu ada)","type":"string"},"scope":{"type":"string"},"token_type":{"example":"Bearer","type":"string"}},"type":"object"},"UserData":{"description":"Profil dasar user dari /api/me","properties":{"email":{"example":"johndoe@ugm.ac.id","type":"string"},"family_name":{"type":"string"},"given_name":{"type":"string"},"name":{"type":"string"},"picture":{"type":"string"},"provider":{"enum":["cas_sso","google","microsoft","facebook","linkedin","apple"],"type":"string"},"username":{"example":"johndoe","type":"string"}},"type":"object"},"UserDetailData":{"description":"Profil lengkap user dari Simaster (/user/me, /user/username/:username)","properties":{"attributes":{"items":{"properties":{"id":{"type":"integer"},"is_default":{"type":"integer"},"user_identitas":{"type":"string"},"user_identitas_id":{"type":"integer"},"user_identitas_nomor":{"type":"string"},"user_tipe":{"type":"string"},"user_tipe_id":{"type":"integer"}},"type":"object"},"type":"array"},"email":{"example":"johndoe@ugm.ac.id","type":"string"},"is_blacklist":{"example":false,"type":"boolean"},"nama_lengkap":{"example":"John Doe","type":"string"},"total_attributes":{"example":3,"type":"integer"},"user_id":{"example":123456,"type":"integer"},"user_identitas":{"example":"ID STAFF","type":"string"},"user_identitas_id":{"example":8,"type":"integer"},"user_identitas_nomor":{"example":"12345","type":"string"},"user_tipe":{"example":"Karyawan","type":"string"},"user_tipe_id":{"example":2,"type":"integer"},"username":{"example":"johndoe","type":"string"}},"type":"object"},"UserInfoResponse":{"description":"OIDC UserInfo claims","properties":{"email":{"type":"string"},"email_verified":{"type":"boolean"},"family_name":{"type":"string"},"given_name":{"type":"string"},"name":{"type":"string"},"picture":{"type":"string"},"preferred_username":{"type":"string"},"sub":{"description":"Username","type":"string"}},"type":"object"}},"securitySchemes":{"BasicAuth":{"description":"client_id:client_secret via Basic Auth header","scheme":"basic","type":"http"},"BearerAuth":{"bearerFormat":"JWT","description":"Access token JWT dari /oauth/token","scheme":"bearer","type":"http"}}},"info":{"description":"Layanan OAuth 2.0 / OIDC Authorization Server untuk mengakses UGM API.\n\n**Grant Types yang didukung:**\n- Authorization Code + PKCE (`authorization_code`)\n- Resource Owner Password (`password`)\n- Client Credentials (`client_credentials`)\n- Refresh Token (`refresh_token`)\n- Google ID Token Exchange (`urn:ietf:params:oauth:grant-type:google_id_token`)\n- RFC 8693 Token Exchange (`urn:ietf:params:oauth:grant-type:token-exchange`)\n\n**Autentikasi client:**\n- `client_secret_basic` \u2014 Basic Auth header\n- `client_secret_post` \u2014 form body\n- `none` \u2014 untuk legacy client (akan dihapus di masa depan)\n","title":"UGM ID","version":"2.0"},"openapi":"3.1.0","paths":{"/.well-known/jwks.json":{"get":{"description":"Public keys untuk verifikasi JWT RS256. Di-cache 5 menit.","operationId":"getJwks","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"keys":{"items":{"properties":{"alg":{"example":"RS256","type":"string"},"e":{"type":"string"},"kid":{"type":"string"},"kty":{"example":"RSA","type":"string"},"n":{"type":"string"},"use":{"example":"sig","type":"string"}},"type":"object"},"type":"array"}},"type":"object"}}},"description":"JWKS berhasil"}},"summary":"JSON Web Key Set (JWKS)","tags":["Discovery"]}},"/.well-known/oauth-authorization-server":{"get":{"operationId":"getOAuthMetadata","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerMetadata"}}},"description":"Metadata server otorisasi OAuth 2.0"}},"summary":"OAuth 2.0 Authorization Server Metadata (RFC 8414)","tags":["Discovery"]}},"/.well-known/openid-configuration":{"get":{"operationId":"getOidcConfiguration","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerMetadata"}}},"description":"Konfigurasi OIDC"}},"summary":"OpenID Connect Discovery","tags":["Discovery"]}},"/api/me":{"get":{"description":"Mengambil username, email, nama, picture, provider. Scope `user.read` diperlukan.","operationId":"getMe","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserData"}}},"description":"Data user berhasil"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"BearerAuth":[]}],"summary":"Data User \u2014 Profil Dasar","tags":["API"]}},"/authorize/apple":{"get":{"description":"Redirect ke Apple Sign In. Apple menggunakan `response_mode=form_post`,\nsehingga callback diterima sebagai POST ke `/callback/apple`.\n","operationId":"authorizeApple","parameters":[{"$ref":"#/components/parameters/OAuthClientId"},{"$ref":"#/components/parameters/OAuthRedirectUri"},{"$ref":"#/components/parameters/OAuthScope"},{"$ref":"#/components/parameters/OAuthState"}],"responses":{"302":{"description":"Redirect ke Apple Sign In"}},"summary":"Login dengan Apple","tags":["Social Login"]}},"/authorize/facebook":{"get":{"operationId":"authorizeFacebook","parameters":[{"$ref":"#/components/parameters/OAuthClientId"},{"$ref":"#/components/parameters/OAuthRedirectUri"},{"$ref":"#/components/parameters/OAuthScope"},{"$ref":"#/components/parameters/OAuthState"}],"responses":{"302":{"description":"Redirect ke Facebook OAuth"}},"summary":"Login dengan Facebook","tags":["Social Login"]}},"/authorize/google":{"get":{"description":"Redirect ke Google OAuth. State dienkripsi AES-GCM.","operationId":"authorizeGoogle","parameters":[{"$ref":"#/components/parameters/OAuthClientId"},{"$ref":"#/components/parameters/OAuthRedirectUri"},{"$ref":"#/components/parameters/OAuthScope"},{"$ref":"#/components/parameters/OAuthState"}],"responses":{"302":{"description":"Redirect ke Google OAuth"}},"summary":"Login dengan Google","tags":["Social Login"]}},"/authorize/linkedin":{"get":{"operationId":"authorizeLinkedin","parameters":[{"$ref":"#/components/parameters/OAuthClientId"},{"$ref":"#/components/parameters/OAuthRedirectUri"},{"$ref":"#/components/parameters/OAuthScope"},{"$ref":"#/components/parameters/OAuthState"}],"responses":{"302":{"description":"Redirect ke LinkedIn OAuth"}},"summary":"Login dengan LinkedIn","tags":["Social Login"]}},"/authorize/microsoft":{"get":{"description":"Redirect ke Microsoft OAuth. Tenant dan domain email divalidasi.","operationId":"authorizeMicrosoft","parameters":[{"$ref":"#/components/parameters/OAuthClientId"},{"$ref":"#/components/parameters/OAuthRedirectUri"},{"$ref":"#/components/parameters/OAuthScope"},{"$ref":"#/components/parameters/OAuthState"}],"responses":{"302":{"description":"Redirect ke Microsoft OAuth"}},"summary":"Login dengan Microsoft 365 UGM","tags":["Social Login"]}},"/callback/apple":{"post":{"description":"Apple mengirim data via HTTP POST (form_post mode).\nMenerima `id_token`, `code`, `state`, dan `user` (hanya first-time login).\n","operationId":"callbackApple","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"properties":{"code":{"type":"string"},"id_token":{"description":"Apple signed JWT","type":"string"},"state":{"type":"string"},"user":{"description":"JSON string (hanya first-time login)","type":"string"}},"type":"object"}}},"required":true},"responses":{"302":{"description":"Redirect ke client redirect_uri dengan authorization code"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Callback Apple Sign In","tags":["Social Login"]}},"/health":{"get":{"description":"Cek status semua layanan. Tidak terkena rate limit.\n- `ok` \u2014 semua layanan normal\n- `degraded` \u2014 Simaster tidak terjangkau (non-fatal)\n- `error` \u2014 primary DB atau Redis down\n","operationId":"healthCheck","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}},"description":"Server sehat"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}},"description":"Server tidak sehat"}},"summary":"Health Check","tags":["Infra"]}},"/oauth/authorize":{"get":{"description":"Memulai Authorization Code flow. Redirect ke CAS SSO UGM, kemudian tampilkan\nconsent screen. PKCE (`code_challenge`) **wajib** disertakan.\n","operationId":"authorizeUser","parameters":[{"in":"query","name":"response_type","required":true,"schema":{"enum":["code"],"type":"string"}},{"in":"query","name":"client_id","required":true,"schema":{"type":"string"}},{"in":"query","name":"redirect_uri","required":true,"schema":{"type":"string"}},{"in":"query","name":"scope","required":false,"schema":{"example":"openid user.read userDetail.read","type":"string"}},{"in":"query","name":"state","required":false,"schema":{"type":"string"}},{"description":"PKCE code challenge (disarankan S256)","in":"query","name":"code_challenge","required":false,"schema":{"type":"string"}},{"in":"query","name":"code_challenge_method","required":false,"schema":{"enum":["S256","plain"],"type":"string"}}],"responses":{"200":{"description":"Halaman consent ditampilkan"},"302":{"description":"Redirect ke CAS SSO atau redirect_uri dengan code"},"400":{"$ref":"#/components/responses/BadRequest"}},"summary":"Otorisasi Pengguna (Authorization Code + PKCE)","tags":["OAuth2"]}},"/oauth/introspect":{"post":{"description":"Cek validitas dan metadata token. Hanya client yang memiliki token tersebut\nyang bisa mengintrospeksi (token.client_id harus sama dengan client yang request).\n","operationId":"introspectToken","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"properties":{"token":{"type":"string"},"token_type_hint":{"enum":["access_token","refresh_token"],"type":"string"}},"required":["token"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntrospectResponse"}}},"description":"Hasil introspeksi"},"401":{"$ref":"#/components/responses/Unauthorized"}},"security":[{"BasicAuth":[]}],"summary":"Introspeksi Token (RFC 7662)","tags":["OAuth2"]}},"/oauth/logout":{"get":{"description":"Logout dari sesi OIDC. Opsional redirect ke `post_logout_redirect_uri`.","operationId":"logoutUser","parameters":[{"in":"query","name":"id_token_hint","required":false,"schema":{"type":"string"}},{"in":"query","name":"post_logout_redirect_uri","required":false,"schema":{"type":"string"}},{"in":"query","name":"state","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Halaman logout berhasil ditampilkan"},"302":{"description":"Redirect ke post_logout_redirect_uri"}},"summary":"RP-Initiated Logout (OIDC)","tags":["OAuth2"]}},"/oauth/revoke":{"post":{"description":"Mencabut access token atau refresh token. Harus menggunakan Basic Auth.","operationId":"revokeToken","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"properties":{"token":{"type":"string"},"token_type_hint":{"enum":["access_token","refresh_token"],"type":"string"}},"required":["token"],"type":"object"}}},"required":true},"responses":{"200":{"description":"Token berhasil dicabut (atau tidak ditemukan \u2014 keduanya mengembalikan 200)"},"401":{"$ref":"#/components/responses/Unauthorized"}},"security":[{"BasicAuth":[]}],"summary":"Cabut Token OAuth2 (RFC 7009)","tags":["OAuth2"]}},"/oauth/token":{"post":{"description":"Mendukung semua grant types. Autentikasi client via Basic Auth atau form body.\n\n**grant_type yang didukung:**\n- `authorization_code` \u2014 tukar code dengan token (wajib `code_verifier` jika pakai PKCE)\n- `password` \u2014 LDAP credentials langsung\n- `client_credentials` \u2014 service-to-service\n- `refresh_token` \u2014 perbarui access token\n- `urn:ietf:params:oauth:grant-type:google_id_token` \u2014 tukar Google ID Token\n- `urn:ietf:params:oauth:grant-type:token-exchange` \u2014 RFC 8693 token exchange\n","operationId":"getToken","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"properties":{"apple_user_payload":{"description":"JSON string user data Apple (hanya first-time login via Apple)","type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string"},"code":{"description":"Authorization code (grant authorization_code)","type":"string"},"code_verifier":{"description":"PKCE verifier (wajib jika menggunakan code_challenge)","type":"string"},"grant_type":{"enum":["authorization_code","password","client_credentials","refresh_token","urn:ietf:params:oauth:grant-type:google_id_token","urn:ietf:params:oauth:grant-type:token-exchange"],"type":"string"},"id_token":{"description":"Google ID Token (grant google_id_token)","type":"string"},"password":{"description":"Hanya untuk grant password","type":"string"},"redirect_uri":{"type":"string"},"refresh_token":{"description":"Hanya untuk grant refresh_token","type":"string"},"scope":{"type":"string"},"subject_issuer":{"description":"Issuer dari subject_token (e.g. https://appleid.apple.com)","example":"https://appleid.apple.com","type":"string"},"subject_token":{"description":"Token untuk ditukar (grant token-exchange)","type":"string"},"subject_token_type":{"description":"Tipe subject_token","example":"urn:ietf:params:oauth:token-type:id_token","type":"string"},"username":{"description":"Hanya untuk grant password","type":"string"}},"required":["grant_type"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}},"description":"Token berhasil diterbitkan"},"400":{"$ref":"#/components/responses/OAuth2Error"},"401":{"$ref":"#/components/responses/Unauthorized"}},"security":[{"BasicAuth":[]},{}],"summary":"Terbitkan Token OAuth2","tags":["OAuth2"]}},"/user/me":{"get":{"description":"Mengambil data lengkap user dari Simaster termasuk `user_tipe`, `identitas`,\ndan `attributes`. Scope `userDetail.read` diperlukan.\n","operationId":"getUserMe","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailData"}}},"description":"Data user lengkap berhasil"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"security":[{"BearerAuth":[]}],"summary":"Data User \u2014 Profil Lengkap (Simaster)","tags":["API"]}},"/user/username/{username}":{"get":{"description":"Mengambil data user berdasarkan username. Scope `user.read` diperlukan.\nMengembalikan 404 jika tidak ditemukan.\n","operationId":"getUserByUsername","parameters":[{"example":"johndoe","in":"path","name":"username","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDetailData"}}},"description":"Data user berhasil"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"User tidak ditemukan"}},"security":[{"BearerAuth":[]}],"summary":"Data User by Username","tags":["API"]}},"/userinfo":{"get":{"description":"Mengambil klaim user. Memerlukan scope `openid`.","operationId":"getUserInfo","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInfoResponse"}}},"description":"Data user berhasil"},"401":{"$ref":"#/components/responses/Unauthorized"}},"security":[{"BearerAuth":[]}],"summary":"OIDC UserInfo Endpoint","tags":["OAuth2"]}}},"servers":[{"description":"Production Server","url":"https://oauth.simaster.ugm.ac.id"},{"description":"Development Server","url":"https://oauth.dev.ugm.ac.id"}],"tags":[{"description":"Endpoint inti OAuth2/OIDC","name":"OAuth2"},{"description":"RFC 8414 & OIDC Discovery","name":"Discovery"},{"description":"Resource API (butuh Bearer token)","name":"API"},{"description":"Social provider login (Google, Microsoft, Facebook, LinkedIn)","name":"Social Login"},{"description":"Health check & infrastruktur","name":"Infra"}]}
