Live video on the AT Protocol

oproxy: implemented PAR

+247 -211
-55
pkg/atproto/client_metadata.go
··· 1 1 package atproto 2 2 3 - import ( 4 - "fmt" 5 - ) 6 - 7 3 var AllowedPlatforms = []string{"ios", "android", "web"} 8 4 9 5 type OAuthClientMetadata struct { ··· 37 33 AuthorizationDetailsTypes []string `json:"authorization_details_types,omitempty"` 38 34 // Jwks *JWKSet `json:"jwks,omitempty"` // You'll need to define JWKSet type 39 35 } 40 - 41 - func boolPtr(b bool) *bool { 42 - return &b 43 - } 44 - 45 - func GetUpstreamMetadata(host string, platform string, appBundleId string) *OAuthClientMetadata { 46 - meta := &OAuthClientMetadata{ 47 - ClientID: fmt.Sprintf("https://%s/api/atproto-oauth/upstream/%s", host, platform), 48 - JwksURI: fmt.Sprintf("https://%s/api/atproto-oauth/jwks.json", host), 49 - ClientURI: fmt.Sprintf("https://%s", host), 50 - // RedirectURIs: []string{fmt.Sprintf("https://%s/login", host)}, 51 - Scope: "atproto transition:generic", 52 - TokenEndpointAuthMethod: "private_key_jwt", 53 - ClientName: "Streamplace", 54 - ResponseTypes: []string{"code"}, 55 - GrantTypes: []string{"authorization_code", "refresh_token"}, 56 - DPoPBoundAccessTokens: boolPtr(true), 57 - TokenEndpointAuthSigningAlg: "ES256", 58 - } 59 - 60 - if platform == "web" { 61 - meta.RedirectURIs = []string{fmt.Sprintf("https://%s/api/oauth/return", host)} 62 - meta.ApplicationType = "web" 63 - } else { 64 - meta.RedirectURIs = []string{fmt.Sprintf("https://%s/api/app-return/%s", host, appBundleId)} 65 - meta.ApplicationType = "native" 66 - } 67 - return meta 68 - } 69 - 70 - func GetDownstreamMetadata(host string, platform string, appBundleId string) *OAuthClientMetadata { 71 - meta := &OAuthClientMetadata{ 72 - ClientID: fmt.Sprintf("https://%s/api/atproto-oauth/downstream/%s", host, platform), 73 - ClientURI: fmt.Sprintf("https://%s", host), 74 - // RedirectURIs: []string{fmt.Sprintf("https://%s/login", host)}, 75 - Scope: "atproto transition:generic", 76 - TokenEndpointAuthMethod: "none", 77 - ClientName: "Streamplace", 78 - ResponseTypes: []string{"code"}, 79 - GrantTypes: []string{"authorization_code", "refresh_token"}, 80 - DPoPBoundAccessTokens: boolPtr(true), 81 - } 82 - if platform == "web" { 83 - meta.RedirectURIs = []string{fmt.Sprintf("https://%s/login", host)} 84 - meta.ApplicationType = "web" 85 - } else { 86 - meta.RedirectURIs = []string{fmt.Sprintf("https://%s/api/app-return/%s", host, appBundleId)} 87 - meta.ApplicationType = "native" 88 - } 89 - return meta 90 - }
+25 -128
pkg/oproxy/handlers.go
··· 2 2 3 3 import ( 4 4 "encoding/json" 5 - "errors" 6 5 "fmt" 7 - "net" 8 6 "net/http" 9 7 "net/url" 10 - "slices" 11 8 "time" 12 9 13 - "github.com/AxisCommunications/go-dpop" 14 10 "github.com/labstack/echo/v4" 15 11 "go.opentelemetry.io/otel" 16 12 "stream.place/streamplace/pkg/atproto" 17 13 "stream.place/streamplace/pkg/log" 18 - "stream.place/streamplace/pkg/model" 19 14 ) 20 15 21 16 func (o *OProxy) Handler() http.Handler { ··· 26 21 o.e.GET("/oauth/return", o.HandleOAuthReturn) 27 22 o.e.POST("/oauth/token", o.HandleOAuthToken) 28 23 o.e.POST("/oauth/revoke", o.HandleOAuthRevoke) 29 - o.e.GET("/oauth/upstream/client-metadata.json", o.HandleATProtoOAuthUpstream) 30 - o.e.GET("/oauth/downstream/client-metadata.json", o.HandleATProtoOAuthDownstream) 24 + o.e.GET("/oauth/upstream/client-metadata.json", o.HandleClientMetadataUpstream) 25 + o.e.GET("/oauth/downstream/client-metadata.json", o.HandleClientMetadataDownstream) 31 26 // prefer to handle this by returning in the metadata blob: 32 27 // apiRouter.GET("/api/atproto-oauth/jwks.json", a.HandleJWKPublic(ctx)) 33 28 return o.e 34 29 } 35 30 36 31 func (o *OProxy) HandleOAuthAuthorizationServer(c echo.Context) error { 37 - w.Header().Set("Access-Control-Allow-Origin", "*") 38 - w.Header().Set("Content-Type", "application/json") 39 - w.WriteHeader(200) 40 - json.NewEncoder(w).Encode(generateOAuthServerMetadata("longos.iameli.link")) 32 + c.Response().Header().Set("Access-Control-Allow-Origin", "*") 33 + c.Response().Header().Set("Content-Type", "application/json") 34 + c.Response().WriteHeader(200) 35 + json.NewEncoder(c.Response().Writer).Encode(generateOAuthServerMetadata("longos.iameli.link")) 36 + return nil 41 37 } 42 38 43 - func (o *OProxy) HandleATProtoOAuthUpstream(c echo.Context) error { 44 - host, _, err := net.SplitHostPort(req.Host) 45 - if err != nil { 46 - host = req.Host 47 - } 48 - if !slices.Contains(atproto.AllowedPlatforms, platform) { 49 - apierrors.WriteHTTPBadRequest(w, "unsupported platform", nil) 50 - return 51 - } 52 - 53 - meta := atproto.GetUpstreamMetadata(host, platform, a.CLI.AppBundleID) 54 - bs, err := json.Marshal(meta) 55 - if err != nil { 56 - apierrors.WriteHTTPInternalServerError(w, "could not marshal metadata", err) 57 - return 58 - } 59 - w.Header().Set("Content-Type", "application/json") 60 - w.Write(bs) 39 + func (o *OProxy) HandleClientMetadataUpstream(c echo.Context) error { 40 + meta := o.GetUpstreamMetadata() 41 + return c.JSON(200, meta) 61 42 } 62 43 63 - func (o *OProxy) HandleATProtoOAuthDownstream(c echo.Context) error { 64 - host, _, err := net.SplitHostPort(req.Host) 65 - if err != nil { 66 - host = req.Host 67 - } 68 - if !slices.Contains(atproto.AllowedPlatforms, platform) { 69 - apierrors.WriteHTTPBadRequest(w, "unsupported platform", nil) 70 - return 71 - } 72 - 73 - meta := atproto.GetDownstreamMetadata(host, platform, a.CLI.AppBundleID) 74 - bs, err := json.Marshal(meta) 75 - if err != nil { 76 - apierrors.WriteHTTPInternalServerError(w, "could not marshal metadata", err) 77 - return 78 - } 79 - w.Header().Set("Content-Type", "application/json") 80 - w.Write(bs) 81 - } 82 - 83 - func generateOAuthServerMetadata(host string) map[string]any { 84 - oauthServerMetadata := map[string]any{ 85 - "issuer": fmt.Sprintf("https://%s", host), 86 - "request_parameter_supported": true, 87 - "request_uri_parameter_supported": true, 88 - "require_request_uri_registration": true, 89 - "scopes_supported": []string{"atproto", "transition:generic", "transition:chat.bsky"}, 90 - "subject_types_supported": []string{"public"}, 91 - "response_types_supported": []string{"code"}, 92 - "response_modes_supported": []string{"query", "fragment", "form_post"}, 93 - "grant_types_supported": []string{"authorization_code", "refresh_token"}, 94 - "code_challenge_methods_supported": []string{"S256"}, 95 - "ui_locales_supported": []string{"en-US"}, 96 - "display_values_supported": []string{"page", "popup", "touch"}, 97 - "authorization_response_iss_parameter_supported": true, 98 - "request_object_encryption_alg_values_supported": []string{}, 99 - "request_object_encryption_enc_values_supported": []string{}, 100 - "jwks_uri": fmt.Sprintf("https://%s/api/oauth/jwks", host), 101 - "authorization_endpoint": fmt.Sprintf("https://%s/api/oauth/authorize", host), 102 - "token_endpoint": fmt.Sprintf("https://%s/api/oauth/token", host), 103 - "token_endpoint_auth_methods_supported": []string{"none", "private_key_jwt"}, 104 - "revocation_endpoint": fmt.Sprintf("https://%s/api/oauth/revoke", host), 105 - "introspection_endpoint": fmt.Sprintf("https://%s/api/oauth/introspect", host), 106 - "pushed_authorization_request_endpoint": fmt.Sprintf("https://%s/api/oauth/par", host), 107 - "require_pushed_authorization_requests": true, 108 - "client_id_metadata_document_supported": true, 109 - "request_object_signing_alg_values_supported": []string{ 110 - "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", 111 - "ES256", "ES256K", "ES384", "ES512", "none", 112 - }, 113 - "token_endpoint_auth_signing_alg_values_supported": []string{ 114 - "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", 115 - "ES256", "ES256K", "ES384", "ES512", 116 - }, 117 - "dpop_signing_alg_values_supported": []string{ 118 - "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", 119 - "ES256", "ES256K", "ES384", "ES512", 120 - }, 121 - } 122 - return oauthServerMetadata 44 + func (o *OProxy) HandleClientMetadataDownstream(c echo.Context) error { 45 + meta := o.GetDownstreamMetadata() 46 + return c.JSON(200, meta) 123 47 } 124 48 125 49 func (o *OProxy) HandleOAuthProtectedResource(c echo.Context) error { 126 - w.Header().Set("Access-Control-Allow-Origin", "*") 127 - w.Header().Set("Content-Type", "application/json") 128 - w.WriteHeader(200) 129 - json.NewEncoder(w).Encode(map[string]interface{}{ 130 - "resource": "https://longos.iameli.link", 50 + return c.JSON(200, map[string]interface{}{ 51 + "resource": fmt.Sprintf("https://%s", o.host), 131 52 "authorization_servers": []string{ 132 - "https://longos.iameli.link", 53 + fmt.Sprintf("https://%s", o.host), 133 54 }, 134 55 "scopes_supported": []string{}, 135 56 "bearer_methods_supported": []string{ ··· 137 58 }, 138 59 "resource_documentation": "https://atproto.com", 139 60 }) 140 - return nil 141 61 } 142 62 143 63 func (o *OProxy) HandleOAuthPAR(c echo.Context) error { 144 - w.Header().Set("Access-Control-Allow-Origin", "*") 145 - w.Header().Set("Content-Type", "application/json") 146 - var par model.PAR 147 - if err := json.NewDecoder(r.Body).Decode(&par); err != nil { 148 - apierrors.WriteHTTPBadRequest(w, "invalid request", err) 149 - return 64 + c.Response().Header().Set("Access-Control-Allow-Origin", "*") 65 + var par PAR 66 + if err := json.NewDecoder(c.Request().Body).Decode(&par); err != nil { 67 + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) 150 68 } 151 69 152 - dpopHeader := r.Header.Get("DPoP") 70 + dpopHeader := c.Request().Header.Get("DPoP") 153 71 if dpopHeader == "" { 154 - apierrors.WriteHTTPBadRequest(w, "DPoP header is required", nil) 155 - return 72 + return echo.NewHTTPError(http.StatusUnauthorized, "DPoP header is required") 156 73 } 157 74 158 - thirtySec := time.Duration(30 * time.Second) 159 - proof, err := dpop.Parse(dpopHeader, dpop.POST, &url.URL{Host: r.Host, Scheme: "https", Path: "/api/oauth/par"}, dpop.ParseOptions{ 160 - Nonce: "", 161 - TimeWindow: &thirtySec, 162 - }) 163 - // Check the error type to determine response 75 + resp, err := o.NewPAR(c.Request().Context(), &par, dpopHeader) 164 76 if err != nil { 165 - if ok := errors.Is(err, dpop.ErrInvalidProof); ok { 166 - apierrors.WriteHTTPBadRequest(w, "invalid DPoP proof", nil) 167 - return 168 - } 169 - apierrors.WriteHTTPBadRequest(w, "invalid DPoP proof", err) 170 - return 77 + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) 171 78 } 172 - 173 - // proof is valid, get public key to associate with access token 174 - par.JKT = proof.PublicKey() 175 - 176 - if err := a.Model.CreatePAR(&par); err != nil { 177 - apierrors.WriteHTTPInternalServerError(w, "could not create par", err) 178 - return 179 - } 180 - resp := par.ToPARResponse() 181 - w.WriteHeader(201) 182 - json.NewEncoder(w).Encode(resp) 79 + return c.JSON(http.StatusCreated, resp) 183 80 } 184 81 185 82 func (o *OProxy) HandleOAuthAuthorize(c echo.Context) error {
+19
pkg/oproxy/helpers.go
··· 1 + package oproxy 2 + 3 + import ( 4 + "fmt" 5 + 6 + "github.com/google/uuid" 7 + ) 8 + 9 + func boolPtr(b bool) *bool { 10 + return &b 11 + } 12 + 13 + func codeUUID(prefix string) string { 14 + uu, err := uuid.NewV7() 15 + if err != nil { 16 + panic(err) 17 + } 18 + return fmt.Sprintf("%s-%s", prefix, uu.String()) 19 + }
+145
pkg/oproxy/oauth_downstream.go
··· 5 5 "crypto/sha256" 6 6 "encoding/base64" 7 7 "fmt" 8 + "net/http" 9 + "net/url" 10 + "slices" 8 11 "time" 9 12 13 + "github.com/AxisCommunications/go-dpop" 10 14 "github.com/golang-jwt/jwt/v5" 11 15 "github.com/google/uuid" 16 + "github.com/labstack/echo/v4" 12 17 "stream.place/streamplace/pkg/config" 13 18 "stream.place/streamplace/pkg/model" 14 19 ) ··· 180 185 } 181 186 return fmt.Sprintf("code-%s", uu.String()), nil 182 187 } 188 + 189 + func generateOAuthServerMetadata(host string) map[string]any { 190 + oauthServerMetadata := map[string]any{ 191 + "issuer": fmt.Sprintf("https://%s", host), 192 + "request_parameter_supported": true, 193 + "request_uri_parameter_supported": true, 194 + "require_request_uri_registration": true, 195 + "scopes_supported": []string{"atproto", "transition:generic", "transition:chat.bsky"}, 196 + "subject_types_supported": []string{"public"}, 197 + "response_types_supported": []string{"code"}, 198 + "response_modes_supported": []string{"query", "fragment", "form_post"}, 199 + "grant_types_supported": []string{"authorization_code", "refresh_token"}, 200 + "code_challenge_methods_supported": []string{"S256"}, 201 + "ui_locales_supported": []string{"en-US"}, 202 + "display_values_supported": []string{"page", "popup", "touch"}, 203 + "authorization_response_iss_parameter_supported": true, 204 + "request_object_encryption_alg_values_supported": []string{}, 205 + "request_object_encryption_enc_values_supported": []string{}, 206 + "jwks_uri": fmt.Sprintf("https://%s/oauth/jwks", host), 207 + "authorization_endpoint": fmt.Sprintf("https://%s/oauth/authorize", host), 208 + "token_endpoint": fmt.Sprintf("https://%s/oauth/token", host), 209 + "token_endpoint_auth_methods_supported": []string{"none", "private_key_jwt"}, 210 + "revocation_endpoint": fmt.Sprintf("https://%s/oauth/revoke", host), 211 + "introspection_endpoint": fmt.Sprintf("https://%s/oauth/introspect", host), 212 + "pushed_authorization_request_endpoint": fmt.Sprintf("https://%s/oauth/par", host), 213 + "require_pushed_authorization_requests": true, 214 + "client_id_metadata_document_supported": true, 215 + "request_object_signing_alg_values_supported": []string{ 216 + "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", 217 + "ES256", "ES256K", "ES384", "ES512", "none", 218 + }, 219 + "token_endpoint_auth_signing_alg_values_supported": []string{ 220 + "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", 221 + "ES256", "ES256K", "ES384", "ES512", 222 + }, 223 + "dpop_signing_alg_values_supported": []string{ 224 + "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", 225 + "ES256", "ES256K", "ES384", "ES512", 226 + }, 227 + } 228 + return oauthServerMetadata 229 + } 230 + 231 + func (o *OProxy) GetDownstreamMetadata() *OAuthClientMetadata { 232 + meta := &OAuthClientMetadata{ 233 + ClientID: fmt.Sprintf("https://%s/oauth/downstream/client-metadata.json", o.host), 234 + ClientURI: fmt.Sprintf("https://%s", o.host), 235 + // RedirectURIs: []string{fmt.Sprintf("https://%s/login", host)}, 236 + Scope: "atproto transition:generic", 237 + TokenEndpointAuthMethod: "none", 238 + ClientName: "Streamplace", 239 + ResponseTypes: []string{"code"}, 240 + GrantTypes: []string{"authorization_code", "refresh_token"}, 241 + DPoPBoundAccessTokens: boolPtr(true), 242 + RedirectURIs: []string{fmt.Sprintf("https://%s/login", o.host)}, 243 + } 244 + return meta 245 + } 246 + 247 + func (o *OProxy) NewPAR(ctx context.Context, par *PAR, dpopHeader string) (*PARResponse, error) { 248 + thirtySec := time.Duration(30 * time.Second) 249 + proof, err := dpop.Parse(dpopHeader, dpop.POST, &url.URL{Host: o.host, Scheme: "https", Path: "/api/oauth/par"}, dpop.ParseOptions{ 250 + Nonce: "", 251 + TimeWindow: &thirtySec, 252 + }) 253 + // Check the error type to determine response 254 + if err != nil { 255 + // if ok := errors.Is(err, dpop.ErrInvalidProof); ok { 256 + // apierrors.WriteHTTPBadRequest(w, "invalid DPoP proof", nil) 257 + // return 258 + // } 259 + // apierrors.WriteHTTPBadRequest(w, "invalid DPoP proof", err) 260 + // return 261 + return nil, err 262 + } 263 + 264 + clientMetadata := o.GetDownstreamMetadata() 265 + if par.ClientID != clientMetadata.ClientID { 266 + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid client_id") 267 + } 268 + 269 + if !slices.Contains(clientMetadata.RedirectURIs, par.RedirectURI) { 270 + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid redirect_uri") 271 + } 272 + 273 + if par.CodeChallengeMethod != "S256" { 274 + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid code challenge method") 275 + } 276 + 277 + if par.ResponseMode != "query" { 278 + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid response mode") 279 + } 280 + 281 + if par.ResponseType != "code" { 282 + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid response type") 283 + } 284 + 285 + if par.Scope != o.scope { 286 + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid scope") 287 + } 288 + 289 + if par.LoginHint == "" { 290 + return nil, echo.NewHTTPError(http.StatusBadRequest, "login hint is required to find your PDS") 291 + } 292 + 293 + if par.State == "" { 294 + return nil, echo.NewHTTPError(http.StatusBadRequest, "state is required") 295 + } 296 + 297 + if par.Scope != o.scope { 298 + return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid scope (expected %s, got %s)", o.scope, par.Scope)) 299 + } 300 + 301 + // proof is valid, get public key to use as primary key of oauth session 302 + jkt := proof.PublicKey() 303 + uu, err := uuid.NewV7() 304 + if err != nil { 305 + panic(err) 306 + } 307 + 308 + urn := fmt.Sprintf("urn:ietf:params:oauth:request_uri:%s", uu.String()) 309 + 310 + err = o.createOAuthSession(jkt, &OAuthSession{ 311 + DownstreamDPoPJKT: jkt, 312 + DownstreamPARRequestURI: urn, 313 + DownstreamCodeChallenge: par.CodeChallenge, 314 + DownstreamState: par.State, 315 + DID: par.LoginHint, 316 + }) 317 + if err != nil { 318 + return nil, fmt.Errorf("could not create oauth session: %w", err) 319 + } 320 + 321 + resp := &PARResponse{ 322 + RequestURI: urn, 323 + ExpiresIn: int(thirtySec.Seconds()), 324 + } 325 + 326 + return resp, nil 327 + }
+6 -5
pkg/oproxy/oauth_session.go
··· 8 8 9 9 // OAuthSession stores authentication data needed during the OAuth flow 10 10 type OAuthSession struct { 11 - RepoDID string `gorm:"column:repo_did;index"` 12 - PDSUrl string `gorm:"column:pds_url"` 11 + DID string `gorm:"column:repo_did;index"` 13 12 14 13 // Upstream fields 15 14 UpstreamState string `gorm:"column:upstream_state;index"` ··· 22 21 UpstreamRefreshToken string `gorm:"column:upstream_refresh_token"` 23 22 24 23 // Downstream fields 25 - DownstreamPARID string `gorm:"column:downstream_par_id;uniqueIndex"` 26 - DownstreamPAR *PAR `gorm:"foreignKey:DownstreamPARID"` 27 24 DownstreamDPoPNonce string `gorm:"column:downstream_dpop_nonce"` 28 - DownstreamDPoPJKT string `gorm:"column:downstream_dpop_jkt"` 25 + DownstreamDPoPJKT string `gorm:"column:downstream_dpop_jkt;primaryKey"` 29 26 DownstreamAccessToken string `gorm:"column:downstream_access_token;index"` 30 27 DownstreamRefreshToken string `gorm:"column:downstream_refresh_token;index"` 31 28 DownstreamAuthorizationCode string `gorm:"column:downstream_authorization_code;index"` 29 + DownstreamState string `gorm:"column:downstream_state"` 30 + DownstreamScope string `gorm:"column:downstream_scope"` 31 + DownstreamCodeChallenge string `gorm:"column:downstream_code_challenge"` 32 + DownstreamPARRequestURI string `gorm:"column:downstream_par_request_uri"` 32 33 33 34 RevokedAt *time.Time `gorm:"column:revoked_at"` 34 35 CreatedAt time.Time
+19
pkg/oproxy/oauth_upstream.go
··· 14 14 "github.com/lestrrat-go/jwx/v2/jwk" 15 15 "stream.place/streamplace/pkg/config" 16 16 "stream.place/streamplace/pkg/log" 17 + "stream.place/streamplace/pkg/model" 17 18 ) 18 19 19 20 func Login(ctx context.Context, cli *config.CLI, downstreamPAR *model.PAR, mod model.Model) (string, error) { ··· 172 173 173 174 return session, nil 174 175 } 176 + 177 + func (o *OProxy) GetUpstreamMetadata() *OAuthClientMetadata { 178 + meta := &OAuthClientMetadata{ 179 + ClientID: fmt.Sprintf("https://%s/api/atproto-oauth/oauth/upstream/client-metadata.json", o.host), 180 + JwksURI: fmt.Sprintf("https://%s/api/atproto-oauth/jwks.json", o.host), 181 + ClientURI: fmt.Sprintf("https://%s", o.host), 182 + // RedirectURIs: []string{fmt.Sprintf("https://%s/login", host)}, 183 + Scope: "atproto transition:generic", 184 + TokenEndpointAuthMethod: "private_key_jwt", 185 + ClientName: "Streamplace", 186 + ResponseTypes: []string{"code"}, 187 + GrantTypes: []string{"authorization_code", "refresh_token"}, 188 + DPoPBoundAccessTokens: boolPtr(true), 189 + TokenEndpointAuthSigningAlg: "ES256", 190 + RedirectURIs: []string{fmt.Sprintf("https://%s/oauth/return", o.host)}, 191 + } 192 + return meta 193 + }
+19 -10
pkg/oproxy/oproxy.go
··· 3 3 import "github.com/labstack/echo/v4" 4 4 5 5 type OProxy struct { 6 - saveOAuthSession func(id string, session *OAuthSession) error 7 - loadOAuthSession func(id string) (*OAuthSession, error) 8 - e *echo.Echo 6 + createOAuthSession func(id string, session *OAuthSession) error 7 + updateOAuthSession func(id string, session *OAuthSession) error 8 + loadOAuthSession func(id string) (*OAuthSession, error) 9 + e *echo.Echo 10 + host string 11 + scope string 9 12 } 10 13 11 - type OProxyConfig struct { 12 - SaveOAuthSession func(id string, session *OAuthSession) error 13 - LoadOAuthSession func(id string) (*OAuthSession, error) 14 + type Config struct { 15 + CreateOAuthSession func(id string, session *OAuthSession) error 16 + UpdateOAuthSession func(id string, session *OAuthSession) error 17 + LoadOAuthSession func(id string) (*OAuthSession, error) 18 + Host string 19 + Scope string 14 20 } 15 21 16 - func NewOProxy(conf *OProxyConfig) *OProxy { 22 + func New(conf *Config) *OProxy { 17 23 e := echo.New() 18 24 return &OProxy{ 19 - saveOAuthSession: conf.SaveOAuthSession, 20 - loadOAuthSession: conf.LoadOAuthSession, 21 - e: e, 25 + createOAuthSession: conf.CreateOAuthSession, 26 + updateOAuthSession: conf.UpdateOAuthSession, 27 + loadOAuthSession: conf.LoadOAuthSession, 28 + e: e, 29 + host: conf.Host, 30 + scope: conf.Scope, 22 31 } 23 32 }
+14 -13
pkg/oproxy/par.go
··· 1 1 package oproxy 2 2 3 - import "time" 3 + type PAR struct { 4 + ClientID string `json:"client_id"` 5 + RedirectURI string `json:"redirect_uri"` 6 + CodeChallenge string `json:"code_challenge"` 7 + CodeChallengeMethod string `json:"code_challenge_method"` 8 + State string `json:"state"` 9 + LoginHint string `json:"login_hint"` 10 + ResponseMode string `json:"response_mode"` 11 + ResponseType string `json:"response_type"` 12 + Scope string `json:"scope"` 13 + } 4 14 5 - type PAR struct { 6 - ClientID string `json:"client_id" gorm:"column:client_id;index"` 7 - RedirectURI string `json:"redirect_uri" gorm:"column:redirect_uri"` 8 - CodeChallenge string `json:"code_challenge" gorm:"column:code_challenge;index"` 9 - CodeChallengeMethod string `json:"code_challenge_method" gorm:"column:code_challenge_method"` 10 - State string `json:"state" gorm:"column:state"` 11 - LoginHint string `json:"login_hint" gorm:"column:login_hint"` 12 - ResponseMode string `json:"response_mode" gorm:"column:response_mode"` 13 - ResponseType string `json:"response_type" gorm:"column:response_type"` 14 - Scope string `json:"scope" gorm:"column:scope"` 15 - ExpiresAt time.Time `json:"expires_at" gorm:"column:expires_at"` 16 - JKT string `json:"jkt" gorm:"column:jkt"` 15 + type PARResponse struct { 16 + RequestURI string `json:"request_uri"` 17 + ExpiresIn int `json:"expires_in"` 17 18 }