This commit is contained in:
@@ -30,13 +30,16 @@ services:
|
||||
"--pg-password","happypostgresuser",
|
||||
# other hosts
|
||||
"--nats-host","nats:4222",
|
||||
"--auth-rpc-host","authrpc:8081",
|
||||
"--data-rpc-host","dataservice:9000",
|
||||
# OIDC. The auth service is no longer on the request path: access tokens
|
||||
# verify locally against the issuer's key set, so there is no auth rpc
|
||||
# host any more. OIDC_ISSUER / OIDC_AUDIENCE / OIDC_CLIENT_ID come from
|
||||
# the shared env file, as they already do for the developer portal and
|
||||
# moderation.
|
||||
]
|
||||
env_file:
|
||||
- /home/quat/auth-compose/strafesnet_staging.env
|
||||
depends_on:
|
||||
- authrpc
|
||||
- nats
|
||||
networks:
|
||||
- maps-service-network
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
package main
|
||||
|
||||
//go:generate go run github.com/swaggo/swag/cmd/swag@latest init -g ./cmd/maps-service/service.go
|
||||
//go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target pkg/api --clean openapi.yaml
|
||||
// Pinned: `@latest` silently regenerates the whole API against whatever ogen
|
||||
// released today. Keep this in step with the ogen version in go.mod.
|
||||
//go:generate go run github.com/ogen-go/ogen/cmd/ogen@v1.23.0 --target pkg/api --clean openapi.yaml
|
||||
|
||||
4
go.mod
4
go.mod
@@ -51,9 +51,11 @@ require (
|
||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.7 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.20.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||
github.com/go-openapi/spec v0.22.6 // indirect
|
||||
@@ -98,12 +100,14 @@ require (
|
||||
golang.org/x/arch v0.29.0 // indirect
|
||||
golang.org/x/crypto v0.54.0 // indirect
|
||||
golang.org/x/mod v0.38.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/tools v0.48.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260715232425-e75dac1f907d // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
git.itzana.me/strafesnet/auth-sdk v0.1.1
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -1,5 +1,7 @@
|
||||
git.itzana.me/StrafesNET/dev-service v0.0.0-20260307022628-fe71e9dbcb0b h1:z0iQFOeFvr8dfsWI8tG3DqMyssHqjU13/p6L9Wrt4MM=
|
||||
git.itzana.me/StrafesNET/dev-service v0.0.0-20260307022628-fe71e9dbcb0b/go.mod h1:KJal0K++M6HEzSry6JJ2iDPZtOQn5zSstNlDbU3X4Jg=
|
||||
git.itzana.me/strafesnet/auth-sdk v0.1.1 h1:4Mlsnop3vdiJnun7548nrO7e6FuJNMBGf9T6Uhx9gCY=
|
||||
git.itzana.me/strafesnet/auth-sdk v0.1.1/go.mod h1:BnwE/YFpXeaPaBNWcGkA2Z53k/qwqJp1uHOOkHhHh6U=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20260414205100-57b3171fc3f2 h1:AIJ6mUohEPblXKk+4oSnWpSokh8C6Jk9MjbzCvHXX1s=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20260414205100-57b3171fc3f2/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
|
||||
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9 h1:7lU6jyR7S7Rhh1dnUp7GyIRHUTBXZagw8F4n4hOyxLw=
|
||||
@@ -56,6 +58,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
|
||||
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
|
||||
github.com/coreos/go-oidc/v3 v3.20.0 h1:EtE0WIBHk03N+DqGkY4+UONzzZHk7amKt6IyNd7OsZE=
|
||||
github.com/coreos/go-oidc/v3 v3.20.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -83,6 +87,8 @@ github.com/go-faster/jx v1.2.0 h1:T2YHJPrFaYu21fJtUxC9GzmluKu8rVIFDwwGBKTDseI=
|
||||
github.com/go-faster/jx v1.2.0/go.mod h1:UWLOVDmMG597a5tBFPLIWJdUxz5/2emOpfsj9Neg0PE=
|
||||
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
|
||||
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -275,6 +281,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
|
||||
63
openapi.yaml
63
openapi.yaml
@@ -27,7 +27,7 @@ tags:
|
||||
- name: Users
|
||||
description: User operations
|
||||
security:
|
||||
- cookieAuth: []
|
||||
- bearerAuth: []
|
||||
paths:
|
||||
/stats:
|
||||
get:
|
||||
@@ -49,6 +49,31 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/session/config:
|
||||
get:
|
||||
summary: Get the OIDC parameters the web client needs to sign in
|
||||
description: >-
|
||||
Unauthenticated by necessity: the client calls this before it has a
|
||||
token, to discover which issuer and client id to use. Contains no
|
||||
secrets — all three values are public parameters of the authorization
|
||||
code flow and are visible in the sign-in redirect.
|
||||
operationId: sessionConfig
|
||||
tags:
|
||||
- Session
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OIDCConfig"
|
||||
default:
|
||||
description: General Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/session/user:
|
||||
get:
|
||||
summary: Get information about the currently logged in user
|
||||
@@ -1776,10 +1801,15 @@ paths:
|
||||
$ref: "#/components/schemas/Error"
|
||||
components:
|
||||
securitySchemes:
|
||||
cookieAuth:
|
||||
type: apiKey
|
||||
in: cookie
|
||||
name: session_id
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: >-
|
||||
StrafesNET OIDC access token, as issued to the maps web client and
|
||||
already used by the developer portal and moderation. Roles ride in the
|
||||
token's roles claim, so no session lookup is needed to authorize a
|
||||
request.
|
||||
parameters:
|
||||
MapID:
|
||||
name: MapID
|
||||
@@ -1944,6 +1974,29 @@ components:
|
||||
AvatarURL:
|
||||
type: string
|
||||
maxLength: 256
|
||||
OIDCConfig:
|
||||
description: >-
|
||||
Public parameters of the authorization code flow. Field names match the
|
||||
keys oidc-client-ts expects, and the shape the developer portal and
|
||||
moderation already serve, so the web clients share one config loader.
|
||||
required:
|
||||
- authority
|
||||
- client_id
|
||||
- audience
|
||||
type: object
|
||||
properties:
|
||||
authority:
|
||||
description: Issuer URL that mints and signs access tokens.
|
||||
type: string
|
||||
maxLength: 256
|
||||
client_id:
|
||||
description: Public client id registered for the maps web application.
|
||||
type: string
|
||||
maxLength: 128
|
||||
audience:
|
||||
description: Audience the maps API requires on an access token.
|
||||
type: string
|
||||
maxLength: 128
|
||||
Map:
|
||||
required:
|
||||
- ID
|
||||
|
||||
@@ -380,6 +380,14 @@ type Invoker interface {
|
||||
//
|
||||
// POST /maps-admin/seed-combobulator
|
||||
SeedCombobulator(ctx context.Context) error
|
||||
// SessionConfig invokes sessionConfig operation.
|
||||
//
|
||||
// Unauthenticated by necessity: the client calls this before it has a token, to discover which issuer
|
||||
// and client id to use. Contains no secrets — all three values are public parameters of the
|
||||
// authorization code flow and are visible in the sign-in redirect.
|
||||
//
|
||||
// GET /session/config
|
||||
SessionConfig(ctx context.Context) (*OIDCConfig, error)
|
||||
// SessionRoles invokes sessionRoles operation.
|
||||
//
|
||||
// Get list of roles for the current session.
|
||||
@@ -563,14 +571,14 @@ func (c *Client) sendActionMapfixAccepted(ctx context.Context, params ActionMapf
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixAcceptedOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixAcceptedOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,14 +703,14 @@ func (c *Client) sendActionMapfixReject(ctx context.Context, params ActionMapfix
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixRejectOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixRejectOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,14 +835,14 @@ func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, params Acti
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixRequestChangesOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixRequestChangesOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,14 +968,14 @@ func (c *Client) sendActionMapfixResetSubmitting(ctx context.Context, params Act
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixResetSubmittingOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixResetSubmittingOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1092,14 +1100,14 @@ func (c *Client) sendActionMapfixRetryValidate(ctx context.Context, params Actio
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixRetryValidateOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixRetryValidateOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1224,14 +1232,14 @@ func (c *Client) sendActionMapfixRevoke(ctx context.Context, params ActionMapfix
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixRevokeOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixRevokeOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1356,14 +1364,14 @@ func (c *Client) sendActionMapfixTriggerRelease(ctx context.Context, params Acti
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixTriggerReleaseOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixTriggerReleaseOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1488,14 +1496,14 @@ func (c *Client) sendActionMapfixTriggerSubmit(ctx context.Context, params Actio
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixTriggerSubmitOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixTriggerSubmitOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1620,14 +1628,14 @@ func (c *Client) sendActionMapfixTriggerSubmitUnchecked(ctx context.Context, par
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixTriggerSubmitUncheckedOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixTriggerSubmitUncheckedOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1752,14 +1760,14 @@ func (c *Client) sendActionMapfixTriggerUpload(ctx context.Context, params Actio
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixTriggerUploadOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixTriggerUploadOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1884,14 +1892,14 @@ func (c *Client) sendActionMapfixTriggerValidate(ctx context.Context, params Act
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixTriggerValidateOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixTriggerValidateOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2016,14 +2024,14 @@ func (c *Client) sendActionMapfixUploaded(ctx context.Context, params ActionMapf
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixUploadedOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixUploadedOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2148,14 +2156,14 @@ func (c *Client) sendActionMapfixValidated(ctx context.Context, params ActionMap
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionMapfixValidatedOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionMapfixValidatedOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2280,14 +2288,14 @@ func (c *Client) sendActionSubmissionAccepted(ctx context.Context, params Action
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionAcceptedOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionAcceptedOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2412,14 +2420,14 @@ func (c *Client) sendActionSubmissionReject(ctx context.Context, params ActionSu
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionRejectOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionRejectOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2544,14 +2552,14 @@ func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, params
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionRequestChangesOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionRequestChangesOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2677,14 +2685,14 @@ func (c *Client) sendActionSubmissionResetSubmitting(ctx context.Context, params
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionResetSubmittingOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionResetSubmittingOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2809,14 +2817,14 @@ func (c *Client) sendActionSubmissionRetryValidate(ctx context.Context, params A
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionRetryValidateOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionRetryValidateOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2941,14 +2949,14 @@ func (c *Client) sendActionSubmissionRevoke(ctx context.Context, params ActionSu
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionRevokeOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionRevokeOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3073,14 +3081,14 @@ func (c *Client) sendActionSubmissionTriggerSubmit(ctx context.Context, params A
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionTriggerSubmitOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionTriggerSubmitOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3205,14 +3213,14 @@ func (c *Client) sendActionSubmissionTriggerSubmitUnchecked(ctx context.Context,
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionTriggerSubmitUncheckedOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionTriggerSubmitUncheckedOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3337,14 +3345,14 @@ func (c *Client) sendActionSubmissionTriggerUpload(ctx context.Context, params A
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionTriggerUploadOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionTriggerUploadOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3469,14 +3477,14 @@ func (c *Client) sendActionSubmissionTriggerValidate(ctx context.Context, params
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionTriggerValidateOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionTriggerValidateOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3602,14 +3610,14 @@ func (c *Client) sendActionSubmissionValidated(ctx context.Context, params Actio
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ActionSubmissionValidatedOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ActionSubmissionValidatedOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3983,14 +3991,14 @@ func (c *Client) sendCombobulateMap(ctx context.Context, params CombobulateMapPa
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, CombobulateMapOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, CombobulateMapOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4099,14 +4107,14 @@ func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixTriggerCre
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, CreateMapfixOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, CreateMapfixOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4234,14 +4242,14 @@ func (c *Client) sendCreateMapfixAuditComment(ctx context.Context, request Creat
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, CreateMapfixAuditCommentOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, CreateMapfixAuditCommentOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4350,14 +4358,14 @@ func (c *Client) sendCreateScript(ctx context.Context, request *ScriptCreate) (r
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, CreateScriptOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, CreateScriptOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4466,14 +4474,14 @@ func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPoli
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, CreateScriptPolicyOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, CreateScriptPolicyOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4582,14 +4590,14 @@ func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionTr
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, CreateSubmissionOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, CreateSubmissionOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4698,14 +4706,14 @@ func (c *Client) sendCreateSubmissionAdmin(ctx context.Context, request *Submiss
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, CreateSubmissionAdminOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, CreateSubmissionAdminOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4833,14 +4841,14 @@ func (c *Client) sendCreateSubmissionAuditComment(ctx context.Context, request C
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, CreateSubmissionAuditCommentOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, CreateSubmissionAuditCommentOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4964,14 +4972,14 @@ func (c *Client) sendDeleteMapfix(ctx context.Context, params DeleteMapfixParams
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, DeleteMapfixOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, DeleteMapfixOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5095,14 +5103,14 @@ func (c *Client) sendDeleteScript(ctx context.Context, params DeleteScriptParams
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, DeleteScriptOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, DeleteScriptOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5226,14 +5234,14 @@ func (c *Client) sendDeleteScriptPolicy(ctx context.Context, params DeleteScript
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, DeleteScriptPolicyOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, DeleteScriptPolicyOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5357,14 +5365,14 @@ func (c *Client) sendDeleteSubmission(ctx context.Context, params DeleteSubmissi
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, DeleteSubmissionOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, DeleteSubmissionOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5489,14 +5497,14 @@ func (c *Client) sendDownloadMapAsset(ctx context.Context, params DownloadMapAss
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, DownloadMapAssetOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, DownloadMapAssetOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5935,14 +5943,14 @@ func (c *Client) sendGetOperation(ctx context.Context, params GetOperationParams
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, GetOperationOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, GetOperationOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7876,14 +7884,14 @@ func (c *Client) sendReleaseSubmissions(ctx context.Context, request []ReleaseIn
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, ReleaseSubmissionsOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, ReleaseSubmissionsOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7989,14 +7997,14 @@ func (c *Client) sendSeedCombobulator(ctx context.Context) (res *SeedCombobulato
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, SeedCombobulatorOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, SeedCombobulatorOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8041,6 +8049,88 @@ func (c *Client) sendSeedCombobulator(ctx context.Context) (res *SeedCombobulato
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SessionConfig invokes sessionConfig operation.
|
||||
//
|
||||
// Unauthenticated by necessity: the client calls this before it has a token, to discover which issuer
|
||||
// and client id to use. Contains no secrets — all three values are public parameters of the
|
||||
// authorization code flow and are visible in the sign-in redirect.
|
||||
//
|
||||
// GET /session/config
|
||||
func (c *Client) SessionConfig(ctx context.Context) (*OIDCConfig, error) {
|
||||
res, err := c.sendSessionConfig(ctx)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (c *Client) sendSessionConfig(ctx context.Context) (res *OIDCConfig, err error) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
otelogen.OperationID("sessionConfig"),
|
||||
semconv.HTTPRequestMethodKey.String("GET"),
|
||||
semconv.URLTemplateKey.String("/session/config"),
|
||||
}
|
||||
otelAttrs = append(otelAttrs, c.cfg.Attributes...)
|
||||
|
||||
// Run stopwatch.
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
// Use floating point division here for higher precision (instead of Millisecond method).
|
||||
elapsedDuration := time.Since(startTime)
|
||||
c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
|
||||
}()
|
||||
|
||||
// Increment request counter.
|
||||
c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
|
||||
|
||||
// Start a span for this request.
|
||||
ctx, span := c.cfg.Tracer.Start(ctx, SessionConfigOperation,
|
||||
trace.WithAttributes(otelAttrs...),
|
||||
clientSpanKind,
|
||||
)
|
||||
// Track stage for error reporting.
|
||||
var stage string
|
||||
defer func() {
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, stage)
|
||||
c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
|
||||
}
|
||||
span.End()
|
||||
}()
|
||||
|
||||
stage = "BuildURL"
|
||||
u := uri.Clone(c.requestURL(ctx))
|
||||
var pathParts [1]string
|
||||
pathParts[0] = "/session/config"
|
||||
uri.AddPathParts(u, pathParts[:]...)
|
||||
|
||||
stage = "EncodeRequest"
|
||||
r, err := ht.NewRequest(ctx, "GET", u)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "create request")
|
||||
}
|
||||
|
||||
stage = "SendRequest"
|
||||
resp, err := c.cfg.Client.Do(r)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "do request")
|
||||
}
|
||||
body := resp.Body
|
||||
defer func() {
|
||||
// Drain the body to EOF before closing, so the underlying
|
||||
// connection can be reused by the Transport regardless of the
|
||||
// response status code. See https://github.com/ogen-go/ogen/issues/1670.
|
||||
_, _ = io.Copy(io.Discard, body)
|
||||
_ = body.Close()
|
||||
}()
|
||||
|
||||
stage = "DecodeResponse"
|
||||
result, err := decodeSessionConfigResponse(resp)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "decode response")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SessionRoles invokes sessionRoles operation.
|
||||
//
|
||||
// Get list of roles for the current session.
|
||||
@@ -8102,14 +8192,14 @@ func (c *Client) sendSessionRoles(ctx context.Context) (res *Roles, err error) {
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, SessionRolesOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, SessionRolesOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8215,14 +8305,14 @@ func (c *Client) sendSessionUser(ctx context.Context) (res *User, err error) {
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, SessionUserOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, SessionUserOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8328,14 +8418,14 @@ func (c *Client) sendSessionValidate(ctx context.Context) (res bool, err error)
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, SessionValidateOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, SessionValidateOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8460,14 +8550,14 @@ func (c *Client) sendSetMapfixCompleted(ctx context.Context, params SetMapfixCom
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, SetMapfixCompletedOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, SetMapfixCompletedOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8592,14 +8682,14 @@ func (c *Client) sendSetSubmissionCompleted(ctx context.Context, params SetSubmi
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, SetSubmissionCompletedOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, SetSubmissionCompletedOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8727,14 +8817,14 @@ func (c *Client) sendUpdateMapfixDescription(ctx context.Context, request Update
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, UpdateMapfixDescriptionOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, UpdateMapfixDescriptionOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8891,14 +8981,14 @@ func (c *Client) sendUpdateMapfixModel(ctx context.Context, params UpdateMapfixM
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, UpdateMapfixModelOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, UpdateMapfixModelOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9025,14 +9115,14 @@ func (c *Client) sendUpdateScript(ctx context.Context, request *ScriptUpdate, pa
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, UpdateScriptOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, UpdateScriptOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9159,14 +9249,14 @@ func (c *Client) sendUpdateScriptPolicy(ctx context.Context, request *ScriptPoli
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, UpdateScriptPolicyOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, UpdateScriptPolicyOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9323,14 +9413,14 @@ func (c *Client) sendUpdateSubmissionModel(ctx context.Context, params UpdateSub
|
||||
type bitset = [1]uint8
|
||||
var satisfied bitset
|
||||
{
|
||||
stage = "Security:CookieAuth"
|
||||
switch err := c.securityCookieAuth(ctx, UpdateSubmissionModelOperation, r); {
|
||||
stage = "Security:BearerAuth"
|
||||
switch err := c.securityBearerAuth(ctx, UpdateSubmissionModelOperation, r); {
|
||||
case err == nil: // if NO error
|
||||
satisfied[0] |= 1 << 0
|
||||
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
|
||||
// Skip this security.
|
||||
default:
|
||||
return res, errors.Wrap(err, "security \"CookieAuth\"")
|
||||
return res, errors.Wrap(err, "security \"BearerAuth\"")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2069,6 +2069,136 @@ func (s *Mapfixes) UnmarshalJSON(data []byte) error {
|
||||
return s.Decode(d)
|
||||
}
|
||||
|
||||
// Encode implements json.Marshaler.
|
||||
func (s *OIDCConfig) Encode(e *jx.Encoder) {
|
||||
e.ObjStart()
|
||||
s.encodeFields(e)
|
||||
e.ObjEnd()
|
||||
}
|
||||
|
||||
// encodeFields encodes fields.
|
||||
func (s *OIDCConfig) encodeFields(e *jx.Encoder) {
|
||||
{
|
||||
e.FieldStart("authority")
|
||||
e.Str(s.Authority)
|
||||
}
|
||||
{
|
||||
e.FieldStart("client_id")
|
||||
e.Str(s.ClientID)
|
||||
}
|
||||
{
|
||||
e.FieldStart("audience")
|
||||
e.Str(s.Audience)
|
||||
}
|
||||
}
|
||||
|
||||
var jsonFieldsNameOfOIDCConfig = [3]string{
|
||||
0: "authority",
|
||||
1: "client_id",
|
||||
2: "audience",
|
||||
}
|
||||
|
||||
// Decode decodes OIDCConfig from json.
|
||||
func (s *OIDCConfig) Decode(d *jx.Decoder) error {
|
||||
if s == nil {
|
||||
return errors.New("invalid: unable to decode OIDCConfig to nil")
|
||||
}
|
||||
var requiredBitSet [1]uint8
|
||||
|
||||
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
|
||||
switch string(k) {
|
||||
case "authority":
|
||||
requiredBitSet[0] |= 1 << 0
|
||||
if err := func() error {
|
||||
v, err := d.Str()
|
||||
s.Authority = string(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"authority\"")
|
||||
}
|
||||
case "client_id":
|
||||
requiredBitSet[0] |= 1 << 1
|
||||
if err := func() error {
|
||||
v, err := d.Str()
|
||||
s.ClientID = string(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"client_id\"")
|
||||
}
|
||||
case "audience":
|
||||
requiredBitSet[0] |= 1 << 2
|
||||
if err := func() error {
|
||||
v, err := d.Str()
|
||||
s.Audience = string(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"audience\"")
|
||||
}
|
||||
default:
|
||||
return d.Skip()
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "decode OIDCConfig")
|
||||
}
|
||||
// Validate required fields.
|
||||
var failures []validate.FieldError
|
||||
for i, mask := range [1]uint8{
|
||||
0b00000111,
|
||||
} {
|
||||
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
|
||||
// Mask only required fields and check equality to mask using XOR.
|
||||
//
|
||||
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
|
||||
// Bits of fields which would be set are actually bits of missed fields.
|
||||
missed := bits.OnesCount8(result)
|
||||
for bitN := 0; bitN < missed; bitN++ {
|
||||
bitIdx := bits.TrailingZeros8(result)
|
||||
fieldIdx := i*8 + bitIdx
|
||||
var name string
|
||||
if fieldIdx < len(jsonFieldsNameOfOIDCConfig) {
|
||||
name = jsonFieldsNameOfOIDCConfig[fieldIdx]
|
||||
} else {
|
||||
name = strconv.Itoa(fieldIdx)
|
||||
}
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: name,
|
||||
Error: validate.ErrFieldRequired,
|
||||
})
|
||||
// Reset bit.
|
||||
result &^= 1 << bitIdx
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements stdjson.Marshaler.
|
||||
func (s *OIDCConfig) MarshalJSON() ([]byte, error) {
|
||||
e := jx.Encoder{}
|
||||
s.Encode(&e)
|
||||
return e.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements stdjson.Unmarshaler.
|
||||
func (s *OIDCConfig) UnmarshalJSON(data []byte) error {
|
||||
d := jx.DecodeBytes(data)
|
||||
return s.Decode(d)
|
||||
}
|
||||
|
||||
// Encode implements json.Marshaler.
|
||||
func (s *Operation) Encode(e *jx.Encoder) {
|
||||
e.ObjStart()
|
||||
|
||||
@@ -64,6 +64,7 @@ const (
|
||||
ListSubmissionsOperation OperationName = "ListSubmissions"
|
||||
ReleaseSubmissionsOperation OperationName = "ReleaseSubmissions"
|
||||
SeedCombobulatorOperation OperationName = "SeedCombobulator"
|
||||
SessionConfigOperation OperationName = "SessionConfig"
|
||||
SessionRolesOperation OperationName = "SessionRoles"
|
||||
SessionUserOperation OperationName = "SessionUser"
|
||||
SessionValidateOperation OperationName = "SessionValidate"
|
||||
|
||||
@@ -4632,6 +4632,107 @@ func decodeSeedCombobulatorResponse(resp *http.Response) (res *SeedCombobulatorN
|
||||
return res, errors.Wrap(defRes, "error")
|
||||
}
|
||||
|
||||
func decodeSessionConfigResponse(resp *http.Response) (res *OIDCConfig, _ error) {
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
// Code 200.
|
||||
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var response OIDCConfig
|
||||
if err := func() error {
|
||||
if err := response.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
// Validate response.
|
||||
if err := func() error {
|
||||
if err := response.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return res, errors.Wrap(err, "validate")
|
||||
}
|
||||
return &response, nil
|
||||
default:
|
||||
return res, validate.InvalidContentType(ct)
|
||||
}
|
||||
}
|
||||
// Convenient error response.
|
||||
defRes, err := func() (res *ErrorStatusCode, err error) {
|
||||
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var response Error
|
||||
if err := func() error {
|
||||
if err := response.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
// Validate response.
|
||||
if err := func() error {
|
||||
if err := response.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return res, errors.Wrap(err, "validate")
|
||||
}
|
||||
return &ErrorStatusCode{
|
||||
StatusCode: resp.StatusCode,
|
||||
Response: response,
|
||||
}, nil
|
||||
default:
|
||||
return res, validate.InvalidContentType(ct)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
|
||||
}
|
||||
return res, errors.Wrap(defRes, "error")
|
||||
}
|
||||
|
||||
func decodeSessionRolesResponse(resp *http.Response) (res *Roles, _ error) {
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
|
||||
@@ -593,6 +593,19 @@ func encodeSeedCombobulatorResponse(response *SeedCombobulatorNoContent, w http.
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeSessionConfigResponse(response *OIDCConfig, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
|
||||
e := new(jx.Encoder)
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeSessionRolesResponse(response *Roles, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
|
||||
@@ -12,37 +12,150 @@ import (
|
||||
|
||||
var (
|
||||
rn46AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn2AllowedHeaders = map[string]string{
|
||||
"DELETE": "Authorization",
|
||||
}
|
||||
rn48AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn85AllowedHeaders = map[string]string{
|
||||
"PATCH": "Content-Type",
|
||||
rn83AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn86AllowedHeaders = map[string]string{
|
||||
"PATCH": "Authorization,Content-Type",
|
||||
}
|
||||
rn87AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn5AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn6AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn18AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn8AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn19AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn3AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn9AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn10AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn12AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn14AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn15AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn16AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn17AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn76AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn45AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn62AllowedHeaders = map[string]string{
|
||||
"GET": "Authorization",
|
||||
}
|
||||
rn67AllowedHeaders = map[string]string{
|
||||
"GET": "Authorization",
|
||||
}
|
||||
rn75AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn52AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn60AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"DELETE": "Authorization",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn50AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn58AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"DELETE": "Authorization",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn79AllowedHeaders = map[string]string{
|
||||
"GET": "Authorization",
|
||||
}
|
||||
rn80AllowedHeaders = map[string]string{
|
||||
"GET": "Authorization",
|
||||
}
|
||||
rn81AllowedHeaders = map[string]string{
|
||||
"GET": "Authorization",
|
||||
}
|
||||
rn53AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn54AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn22AllowedHeaders = map[string]string{
|
||||
"DELETE": "Authorization",
|
||||
}
|
||||
rn56AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
"POST": "Authorization,Content-Type",
|
||||
}
|
||||
rn85AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn88AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn25AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn26AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn28AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn37AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn23AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn29AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn30AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn32AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn33AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn35AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn36AllowedHeaders = map[string]string{
|
||||
"POST": "Authorization",
|
||||
}
|
||||
rn38AllowedHeaders = map[string]string{
|
||||
"POST": "Content-Type",
|
||||
@@ -174,7 +287,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "DELETE,GET",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn2AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -279,7 +392,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn83AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -308,7 +421,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "PATCH",
|
||||
allowedHeaders: rn85AllowedHeaders,
|
||||
allowedHeaders: rn86AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "text/plain",
|
||||
})
|
||||
@@ -335,7 +448,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn87AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -386,7 +499,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn5AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -413,7 +526,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn6AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -452,7 +565,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn18AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -479,7 +592,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn8AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -506,7 +619,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn19AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -533,7 +646,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn3AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -562,7 +675,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn9AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -589,7 +702,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn10AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -630,7 +743,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn12AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -656,7 +769,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn14AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -683,7 +796,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn15AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -712,7 +825,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn16AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -739,7 +852,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn17AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -798,7 +911,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn76AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -872,7 +985,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn45AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -899,7 +1012,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "GET",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn62AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -943,7 +1056,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "GET",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn67AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1159,6 +1272,31 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
break
|
||||
}
|
||||
switch elem[0] {
|
||||
case 'c': // Prefix: "config"
|
||||
|
||||
if l := len("config"); len(elem) >= l && elem[0:l] == "config" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
s.handleSessionConfigRequest([0]string{}, elemIsEscaped, w, r)
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "GET",
|
||||
allowedHeaders: nil,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case 'r': // Prefix: "roles"
|
||||
|
||||
if l := len("roles"); len(elem) >= l && elem[0:l] == "roles" {
|
||||
@@ -1175,7 +1313,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "GET",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn79AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1200,7 +1338,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "GET",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn80AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1225,7 +1363,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "GET",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn81AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1342,7 +1480,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "DELETE,GET",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn22AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1447,7 +1585,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn85AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1476,7 +1614,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn88AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1527,7 +1665,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn25AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1554,7 +1692,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn26AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1593,7 +1731,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn28AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1620,7 +1758,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn37AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1647,7 +1785,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn23AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1676,7 +1814,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn29AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1703,7 +1841,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn30AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1743,7 +1881,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn32AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1770,7 +1908,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn33AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1799,7 +1937,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn35AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -1826,7 +1964,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
s.notAllowed(w, r, notAllowedParams{
|
||||
allowedMethods: "POST",
|
||||
allowedHeaders: nil,
|
||||
allowedHeaders: rn36AllowedHeaders,
|
||||
acceptPost: "",
|
||||
acceptPatch: "",
|
||||
})
|
||||
@@ -3185,6 +3323,31 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
|
||||
break
|
||||
}
|
||||
switch elem[0] {
|
||||
case 'c': // Prefix: "config"
|
||||
|
||||
if l := len("config"); len(elem) >= l && elem[0:l] == "config" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch method {
|
||||
case "GET":
|
||||
r.name = SessionConfigOperation
|
||||
r.summary = "Get the OIDC parameters the web client needs to sign in"
|
||||
r.operationID = "sessionConfig"
|
||||
r.operationGroup = ""
|
||||
r.pathPattern = "/session/config"
|
||||
r.args = args
|
||||
r.count = 0
|
||||
return r, true
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
case 'r': // Prefix: "roles"
|
||||
|
||||
if l := len("roles"); len(elem) >= l && elem[0:l] == "roles" {
|
||||
|
||||
@@ -441,34 +441,34 @@ func (s *BatchUsernamesReq) SetUserIds(val []uint64) {
|
||||
s.UserIds = val
|
||||
}
|
||||
|
||||
// CombobulateMapNoContent is response for CombobulateMap operation.
|
||||
type CombobulateMapNoContent struct{}
|
||||
|
||||
type CookieAuth struct {
|
||||
APIKey string
|
||||
Roles []string
|
||||
type BearerAuth struct {
|
||||
Token string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
// GetAPIKey returns the value of APIKey.
|
||||
func (s *CookieAuth) GetAPIKey() string {
|
||||
return s.APIKey
|
||||
// GetToken returns the value of Token.
|
||||
func (s *BearerAuth) GetToken() string {
|
||||
return s.Token
|
||||
}
|
||||
|
||||
// GetRoles returns the value of Roles.
|
||||
func (s *CookieAuth) GetRoles() []string {
|
||||
func (s *BearerAuth) GetRoles() []string {
|
||||
return s.Roles
|
||||
}
|
||||
|
||||
// SetAPIKey sets the value of APIKey.
|
||||
func (s *CookieAuth) SetAPIKey(val string) {
|
||||
s.APIKey = val
|
||||
// SetToken sets the value of Token.
|
||||
func (s *BearerAuth) SetToken(val string) {
|
||||
s.Token = val
|
||||
}
|
||||
|
||||
// SetRoles sets the value of Roles.
|
||||
func (s *CookieAuth) SetRoles(val []string) {
|
||||
func (s *BearerAuth) SetRoles(val []string) {
|
||||
s.Roles = val
|
||||
}
|
||||
|
||||
// CombobulateMapNoContent is response for CombobulateMap operation.
|
||||
type CombobulateMapNoContent struct{}
|
||||
|
||||
// CreateMapfixAuditCommentNoContent is response for CreateMapfixAuditComment operation.
|
||||
type CreateMapfixAuditCommentNoContent struct{}
|
||||
|
||||
@@ -1076,6 +1076,49 @@ func (s *Mapfixes) SetMapfixes(val []Mapfix) {
|
||||
s.Mapfixes = val
|
||||
}
|
||||
|
||||
// Public parameters of the authorization code flow. Field names match the keys oidc-client-ts expects,
|
||||
// and the shape the developer portal and moderation already serve, so the web clients share one config
|
||||
// loader.
|
||||
// Ref: #/components/schemas/OIDCConfig
|
||||
type OIDCConfig struct {
|
||||
// Issuer URL that mints and signs access tokens.
|
||||
Authority string `json:"authority"`
|
||||
// Public client id registered for the maps web application.
|
||||
ClientID string `json:"client_id"`
|
||||
// Audience the maps API requires on an access token.
|
||||
Audience string `json:"audience"`
|
||||
}
|
||||
|
||||
// GetAuthority returns the value of Authority.
|
||||
func (s *OIDCConfig) GetAuthority() string {
|
||||
return s.Authority
|
||||
}
|
||||
|
||||
// GetClientID returns the value of ClientID.
|
||||
func (s *OIDCConfig) GetClientID() string {
|
||||
return s.ClientID
|
||||
}
|
||||
|
||||
// GetAudience returns the value of Audience.
|
||||
func (s *OIDCConfig) GetAudience() string {
|
||||
return s.Audience
|
||||
}
|
||||
|
||||
// SetAuthority sets the value of Authority.
|
||||
func (s *OIDCConfig) SetAuthority(val string) {
|
||||
s.Authority = val
|
||||
}
|
||||
|
||||
// SetClientID sets the value of ClientID.
|
||||
func (s *OIDCConfig) SetClientID(val string) {
|
||||
s.ClientID = val
|
||||
}
|
||||
|
||||
// SetAudience sets the value of Audience.
|
||||
func (s *OIDCConfig) SetAudience(val string) {
|
||||
s.Audience = val
|
||||
}
|
||||
|
||||
// Ref: #/components/schemas/Operation
|
||||
type Operation struct {
|
||||
OperationID int32 `json:"OperationID"`
|
||||
|
||||
@@ -13,8 +13,11 @@ import (
|
||||
|
||||
// SecurityHandler is handler for security parameters.
|
||||
type SecurityHandler interface {
|
||||
// HandleCookieAuth handles cookieAuth security.
|
||||
HandleCookieAuth(ctx context.Context, operationName OperationName, t CookieAuth) (context.Context, error)
|
||||
// HandleBearerAuth handles bearerAuth security.
|
||||
// StrafesNET OIDC access token, as issued to the maps web client and already used by the developer
|
||||
// portal and moderation. Roles ride in the token's roles claim, so no session lookup is needed to
|
||||
// authorize a request.
|
||||
HandleBearerAuth(ctx context.Context, operationName OperationName, t BearerAuth) (context.Context, error)
|
||||
}
|
||||
|
||||
func findAuthorization(h http.Header, prefix string) (string, bool) {
|
||||
@@ -32,8 +35,8 @@ func findAuthorization(h http.Header, prefix string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// operationRolesCookieAuth is a private map storing roles per operation.
|
||||
var operationRolesCookieAuth = map[string][]string{
|
||||
// operationRolesBearerAuth is a private map storing roles per operation.
|
||||
var operationRolesBearerAuth = map[string][]string{
|
||||
ActionMapfixAcceptedOperation: []string{},
|
||||
ActionMapfixRejectOperation: []string{},
|
||||
ActionMapfixRequestChangesOperation: []string{},
|
||||
@@ -86,18 +89,18 @@ var operationRolesCookieAuth = map[string][]string{
|
||||
UpdateSubmissionModelOperation: []string{},
|
||||
}
|
||||
|
||||
// GetRolesForCookieAuth returns the required roles for the given operation.
|
||||
// GetRolesForBearerAuth returns the required roles for the given operation.
|
||||
//
|
||||
// This is useful for authorization scenarios where you need to know which roles
|
||||
// are required for an operation.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// requiredRoles := GetRolesForCookieAuth(AddPetOperation)
|
||||
// requiredRoles := GetRolesForBearerAuth(AddPetOperation)
|
||||
//
|
||||
// Returns nil if the operation has no role requirements or if the operation is unknown.
|
||||
func GetRolesForCookieAuth(operation string) []string {
|
||||
roles, ok := operationRolesCookieAuth[operation]
|
||||
func GetRolesForBearerAuth(operation string) []string {
|
||||
roles, ok := operationRolesBearerAuth[operation]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@@ -107,21 +110,15 @@ func GetRolesForCookieAuth(operation string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Server) securityCookieAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) {
|
||||
var t CookieAuth
|
||||
const parameterName = "session_id"
|
||||
var value string
|
||||
switch cookie, err := req.Cookie(parameterName); {
|
||||
case err == nil: // if NO error
|
||||
value = cookie.Value
|
||||
case errors.Is(err, http.ErrNoCookie):
|
||||
func (s *Server) securityBearerAuth(ctx context.Context, operationName OperationName, req *http.Request) (context.Context, bool, error) {
|
||||
var t BearerAuth
|
||||
token, ok := findAuthorization(req.Header, "Bearer")
|
||||
if !ok {
|
||||
return ctx, false, nil
|
||||
default:
|
||||
return nil, false, errors.Wrap(err, "get cookie value")
|
||||
}
|
||||
t.APIKey = value
|
||||
t.Roles = operationRolesCookieAuth[operationName]
|
||||
rctx, err := s.sec.HandleCookieAuth(ctx, operationName, t)
|
||||
t.Token = token
|
||||
t.Roles = operationRolesBearerAuth[operationName]
|
||||
rctx, err := s.sec.HandleBearerAuth(ctx, operationName, t)
|
||||
if errors.Is(err, ogenerrors.ErrSkipServerSecurity) {
|
||||
return nil, false, nil
|
||||
} else if err != nil {
|
||||
@@ -132,18 +129,18 @@ func (s *Server) securityCookieAuth(ctx context.Context, operationName Operation
|
||||
|
||||
// SecuritySource is provider of security values (tokens, passwords, etc.).
|
||||
type SecuritySource interface {
|
||||
// CookieAuth provides cookieAuth security value.
|
||||
CookieAuth(ctx context.Context, operationName OperationName) (CookieAuth, error)
|
||||
// BearerAuth provides bearerAuth security value.
|
||||
// StrafesNET OIDC access token, as issued to the maps web client and already used by the developer
|
||||
// portal and moderation. Roles ride in the token's roles claim, so no session lookup is needed to
|
||||
// authorize a request.
|
||||
BearerAuth(ctx context.Context, operationName OperationName) (BearerAuth, error)
|
||||
}
|
||||
|
||||
func (s *Client) securityCookieAuth(ctx context.Context, operationName OperationName, req *http.Request) error {
|
||||
t, err := s.sec.CookieAuth(ctx, operationName)
|
||||
func (s *Client) securityBearerAuth(ctx context.Context, operationName OperationName, req *http.Request) error {
|
||||
t, err := s.sec.BearerAuth(ctx, operationName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "security source \"CookieAuth\"")
|
||||
return errors.Wrap(err, "security source \"BearerAuth\"")
|
||||
}
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "session_id",
|
||||
Value: t.APIKey,
|
||||
})
|
||||
req.Header.Set("Authorization", "Bearer "+t.Token)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -359,6 +359,14 @@ type Handler interface {
|
||||
//
|
||||
// POST /maps-admin/seed-combobulator
|
||||
SeedCombobulator(ctx context.Context) error
|
||||
// SessionConfig implements sessionConfig operation.
|
||||
//
|
||||
// Unauthenticated by necessity: the client calls this before it has a token, to discover which issuer
|
||||
// and client id to use. Contains no secrets — all three values are public parameters of the
|
||||
// authorization code flow and are visible in the sign-in redirect.
|
||||
//
|
||||
// GET /session/config
|
||||
SessionConfig(ctx context.Context) (*OIDCConfig, error)
|
||||
// SessionRoles implements sessionRoles operation.
|
||||
//
|
||||
// Get list of roles for the current session.
|
||||
|
||||
@@ -538,6 +538,17 @@ func (UnimplementedHandler) SeedCombobulator(ctx context.Context) error {
|
||||
return ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// SessionConfig implements sessionConfig operation.
|
||||
//
|
||||
// Unauthenticated by necessity: the client calls this before it has a token, to discover which issuer
|
||||
// and client id to use. Contains no secrets — all three values are public parameters of the
|
||||
// authorization code flow and are visible in the sign-in redirect.
|
||||
//
|
||||
// GET /session/config
|
||||
func (UnimplementedHandler) SessionConfig(ctx context.Context) (r *OIDCConfig, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// SessionRoles implements sessionRoles operation.
|
||||
//
|
||||
// Get list of roles for the current session.
|
||||
|
||||
@@ -866,6 +866,87 @@ func (s *Mapfixes) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OIDCConfig) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
MinLengthSet: false,
|
||||
MaxLength: 256,
|
||||
MaxLengthSet: true,
|
||||
Email: false,
|
||||
Hostname: false,
|
||||
Regex: nil,
|
||||
MinNumeric: 0,
|
||||
MinNumericSet: false,
|
||||
MaxNumeric: 0,
|
||||
MaxNumericSet: false,
|
||||
}).Validate(string(s.Authority)); err != nil {
|
||||
return errors.Wrap(err, "string")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "authority",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
MinLengthSet: false,
|
||||
MaxLength: 128,
|
||||
MaxLengthSet: true,
|
||||
Email: false,
|
||||
Hostname: false,
|
||||
Regex: nil,
|
||||
MinNumeric: 0,
|
||||
MinNumericSet: false,
|
||||
MaxNumeric: 0,
|
||||
MaxNumericSet: false,
|
||||
}).Validate(string(s.ClientID)); err != nil {
|
||||
return errors.Wrap(err, "string")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "client_id",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
MinLengthSet: false,
|
||||
MaxLength: 128,
|
||||
MaxLengthSet: true,
|
||||
Email: false,
|
||||
Hostname: false,
|
||||
Regex: nil,
|
||||
MinNumeric: 0,
|
||||
MinNumericSet: false,
|
||||
MaxNumeric: 0,
|
||||
MaxNumericSet: false,
|
||||
}).Validate(string(s.Audience)); err != nil {
|
||||
return errors.Wrap(err, "string")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "audience",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Operation) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/auth"
|
||||
"git.itzana.me/strafesnet/auth-sdk/oidcmiddleware"
|
||||
"git.itzana.me/strafesnet/go-grpc/maps"
|
||||
"git.itzana.me/strafesnet/go-grpc/maps_extended"
|
||||
"git.itzana.me/strafesnet/go-grpc/mapfixes"
|
||||
@@ -84,10 +84,22 @@ func NewServeCommand() *cli.Command {
|
||||
EnvVars: []string{"PORT_INTERNAL"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "auth-rpc-host",
|
||||
Usage: "Host of auth rpc",
|
||||
EnvVars: []string{"AUTH_RPC_HOST"},
|
||||
Value: "auth-service:8090",
|
||||
Name: "oidc-issuer",
|
||||
Usage: "OIDC issuer URL",
|
||||
EnvVars: []string{"OIDC_ISSUER"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "oidc-audience",
|
||||
Usage: "Audience required on maps API access tokens",
|
||||
EnvVars: []string{"OIDC_AUDIENCE"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "oidc-client-id",
|
||||
Usage: "Public OIDC client ID used by the maps web application",
|
||||
EnvVars: []string{"OIDC_CLIENT_ID"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "data-rpc-host",
|
||||
@@ -205,15 +217,25 @@ func serve(ctx *cli.Context) error {
|
||||
HttpClient: http.DefaultClient,
|
||||
ApiKey: ctx.String("rbx-api-key"),
|
||||
},
|
||||
api.OIDCConfig{
|
||||
Authority: ctx.String("oidc-issuer"),
|
||||
ClientID: ctx.String("oidc-client-id"),
|
||||
Audience: ctx.String("oidc-audience"),
|
||||
},
|
||||
)
|
||||
|
||||
conn, err = grpc.Dial(ctx.String("auth-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
// Access tokens verify locally against the issuer's key set, so the auth
|
||||
// service is no longer on the request path — the gRPC connection it needed
|
||||
// is gone with the session cookie it served.
|
||||
authenticator, err := oidcmiddleware.New(
|
||||
ctx.Context,
|
||||
ctx.String("oidc-issuer"),
|
||||
ctx.String("oidc-audience"),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sec := web_api.SecurityHandler{
|
||||
Client: auth.NewAuthServiceClient(conn),
|
||||
log.WithError(err).Fatal("failed to initialize OIDC authenticator")
|
||||
}
|
||||
sec := web_api.SecurityHandler{Authenticator: authenticator}
|
||||
|
||||
srv_external, err := api.NewServer(&svc_external, sec, api.WithPathPrefix("/v1"))
|
||||
if err != nil {
|
||||
|
||||
@@ -4,89 +4,78 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/auth"
|
||||
"git.itzana.me/strafesnet/auth-sdk/oidcmiddleware"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMissingSessionID there is no session id
|
||||
ErrMissingSessionID = fmt.Errorf("%w: SessionID missing", ErrUserInfo)
|
||||
// ErrInvalidSession caller does not have a valid session
|
||||
ErrInvalidSession = fmt.Errorf("%w: Session invalid", ErrUserInfo)
|
||||
// ErrMissingToken there is no bearer token on the request
|
||||
ErrMissingToken = fmt.Errorf("%w: bearer token missing", ErrUserInfo)
|
||||
// ErrInvalidToken the bearer token did not verify against the issuer
|
||||
ErrInvalidToken = fmt.Errorf("%w: bearer token invalid", ErrUserInfo)
|
||||
// ErrUnverifiedRoblox the token is valid but carries no linked Roblox account
|
||||
ErrUnverifiedRoblox = fmt.Errorf("%w: token has no verified Roblox account", ErrUserInfo)
|
||||
)
|
||||
|
||||
// UserInfoHandle is the authenticated caller for one request.
|
||||
//
|
||||
// Everything needed to authorize a request rides in the access token, so unlike
|
||||
// the session-cookie flow this replaces, nothing here leaves the process: no
|
||||
// call is made to answer "who is this" or "what may they do". Display fields
|
||||
// are not token claims and are resolved by SessionUser, the one endpoint that
|
||||
// returns them.
|
||||
type UserInfoHandle struct {
|
||||
// Would love to know a better way to do this
|
||||
svc *SecurityHandler
|
||||
ctx *context.Context
|
||||
sessionId string
|
||||
user *oidcmiddleware.User
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
UserID uint64
|
||||
Username string
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
func (usr UserInfoHandle) GetUserInfo() (userInfo UserInfo, err error) {
|
||||
session, err := usr.svc.Client.GetSessionUser(*usr.ctx, &auth.IdMessage{
|
||||
SessionID: usr.sessionId,
|
||||
})
|
||||
if err != nil {
|
||||
return userInfo, err
|
||||
}
|
||||
userInfo.UserID = session.UserID
|
||||
userInfo.Username = session.Username
|
||||
userInfo.AvatarURL = session.AvatarURL
|
||||
return userInfo, nil
|
||||
}
|
||||
func (usr UserInfoHandle) GetUserID() (uint64, error) {
|
||||
session, err := usr.svc.Client.GetSessionUser(*usr.ctx, &auth.IdMessage{
|
||||
SessionID: usr.sessionId,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return session.UserID, nil
|
||||
return usr.user.RobloxUserID, nil
|
||||
}
|
||||
|
||||
// Validate reports whether the caller still holds a usable identity.
|
||||
//
|
||||
// The token's signature, issuer, audience and expiry were already checked
|
||||
// before this handle existed, so reaching here means the credential is good.
|
||||
func (usr UserInfoHandle) Validate() (bool, error) {
|
||||
validate, err := usr.svc.Client.ValidateSession(*usr.ctx, &auth.IdMessage{
|
||||
SessionID: usr.sessionId,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return validate.Valid, nil
|
||||
return usr.user != nil && usr.user.RobloxVerified, nil
|
||||
}
|
||||
|
||||
func (usr UserInfoHandle) HasRoles(wantRoles model.Roles) (bool, error) {
|
||||
haveroles, err := usr.GetRoles()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return haveroles & wantRoles == wantRoles, nil
|
||||
return haveroles&wantRoles == wantRoles, nil
|
||||
}
|
||||
func (usr UserInfoHandle) GetRoles() (model.Roles, error) {
|
||||
roles, err := usr.svc.Client.GetGroupRole(*usr.ctx, &auth.IdMessage{
|
||||
SessionID: usr.sessionId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return model.RolesEmpty, err
|
||||
// GetRoles folds the token's StrafesNET roles into the permission bitflag.
|
||||
//
|
||||
// The rank ladder is unchanged from the session flow — the ranks now arrive as
|
||||
// a token claim rather than a GetGroupRole round trip.
|
||||
func (usr UserInfoHandle) GetRoles() (model.Roles, error) {
|
||||
if usr.user == nil {
|
||||
return model.RolesEmpty, nil
|
||||
}
|
||||
|
||||
// map roles into bitflag
|
||||
rolesBitflag := model.RolesEmpty;
|
||||
for _, r := range roles.Roles {
|
||||
switch model.GroupRole(r.Rank){
|
||||
rolesBitflag := model.RolesEmpty
|
||||
for _, r := range usr.user.Roles {
|
||||
switch model.GroupRole(r.Rank) {
|
||||
case model.RoleQuat, model.RoleItzaname, model.RoleStagingDeveloper:
|
||||
rolesBitflag|=model.RolesAll
|
||||
rolesBitflag |= model.RolesAll
|
||||
case model.RoleMapAdmin:
|
||||
rolesBitflag|=model.RolesMapAdmin
|
||||
rolesBitflag |= model.RolesMapAdmin
|
||||
case model.RoleMapCouncil:
|
||||
rolesBitflag|=model.RolesMapCouncil
|
||||
rolesBitflag |= model.RolesMapCouncil
|
||||
case model.RoleMapAccess:
|
||||
rolesBitflag|=model.RolesMapAccess
|
||||
rolesBitflag |= model.RolesMapAccess
|
||||
}
|
||||
}
|
||||
return rolesBitflag, nil
|
||||
@@ -117,6 +106,7 @@ func (usr UserInfoHandle) HasRoleSubmissionReview() (bool, error) {
|
||||
func (usr UserInfoHandle) HasRoleScriptWrite() (bool, error) {
|
||||
return usr.HasRoles(model.RolesScriptWrite)
|
||||
}
|
||||
|
||||
/// Not implemented
|
||||
func (usr UserInfoHandle) HasRoleMaptest() (bool, error) {
|
||||
println("HasRoleMaptest is not implemented!")
|
||||
@@ -124,20 +114,33 @@ func (usr UserInfoHandle) HasRoleMaptest() (bool, error) {
|
||||
}
|
||||
|
||||
type SecurityHandler struct {
|
||||
Client auth.AuthServiceClient
|
||||
// Authenticator verifies access tokens against the StrafesNET issuer.
|
||||
Authenticator *oidcmiddleware.Middleware
|
||||
}
|
||||
|
||||
func (svc SecurityHandler) HandleCookieAuth(ctx context.Context, operationName api.OperationName, t api.CookieAuth) (context.Context, error) {
|
||||
sessionId := t.GetAPIKey()
|
||||
if sessionId == "" {
|
||||
return nil, ErrMissingSessionID
|
||||
// HandleBearerAuth verifies the access token and attaches the caller.
|
||||
//
|
||||
// This replaces HandleCookieAuth. The session cookie carried only an opaque id
|
||||
// that had to be exchanged with the auth service on every permission check;
|
||||
// the access token is self-describing and verified locally against the
|
||||
// issuer's key set.
|
||||
func (svc SecurityHandler) HandleBearerAuth(ctx context.Context, operationName api.OperationName, t api.BearerAuth) (context.Context, error) {
|
||||
token := t.GetToken()
|
||||
if token == "" {
|
||||
return nil, ErrMissingToken
|
||||
}
|
||||
|
||||
newCtx := context.WithValue(ctx, "UserInfo", UserInfoHandle{
|
||||
svc: &svc,
|
||||
ctx: &ctx,
|
||||
sessionId: sessionId,
|
||||
})
|
||||
user, err := svc.Authenticator.Verify(ctx, token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrInvalidToken, err)
|
||||
}
|
||||
|
||||
return newCtx, nil
|
||||
// Every operation here is keyed to a Roblox identity. A token without one
|
||||
// verified cannot be mapped to a submitter, so reject it at the edge
|
||||
// rather than letting it fail deeper with a confusing error.
|
||||
if !user.RobloxVerified {
|
||||
return nil, ErrUnverifiedRoblox
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, "UserInfo", UserInfoHandle{user: user}), nil
|
||||
}
|
||||
|
||||
153
pkg/web_api/security_test.go
Normal file
153
pkg/web_api/security_test.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package web_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.itzana.me/strafesnet/auth-sdk/oidcmiddleware"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
)
|
||||
|
||||
// The rank ladder moved from a GetGroupRole gRPC response to an access-token
|
||||
// claim during the OIDC migration. The mapping is what decides whether someone
|
||||
// may release a map, so it is checked directly rather than inferred from a
|
||||
// screen.
|
||||
func TestGetRolesMapsRankLadderToBitflag(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
ranks []int
|
||||
want model.Roles
|
||||
}{
|
||||
{"no roles", nil, model.RolesEmpty},
|
||||
{"unknown rank grants nothing", []int{1, 7, 31}, model.RolesEmpty},
|
||||
{"map access", []int{int(model.RoleMapAccess)}, model.RolesMapAccess},
|
||||
{"map council", []int{int(model.RoleMapCouncil)}, model.RolesMapCouncil},
|
||||
{"map admin", []int{int(model.RoleMapAdmin)}, model.RolesMapAdmin},
|
||||
{"quat is everything", []int{int(model.RoleQuat)}, model.RolesAll},
|
||||
{"itzaname is everything", []int{int(model.RoleItzaname)}, model.RolesAll},
|
||||
{"staging developer is everything", []int{int(model.RoleStagingDeveloper)}, model.RolesAll},
|
||||
{
|
||||
"multiple roles union",
|
||||
[]int{int(model.RoleMapAccess), int(model.RoleMapCouncil)},
|
||||
model.RolesMapAccess | model.RolesMapCouncil,
|
||||
},
|
||||
{
|
||||
"an unknown rank never dilutes a known one",
|
||||
[]int{int(model.RoleMapAdmin), 3},
|
||||
model.RolesMapAdmin,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
roles := make([]oidcmiddleware.Role, 0, len(testCase.ranks))
|
||||
for _, rank := range testCase.ranks {
|
||||
roles = append(roles, oidcmiddleware.Role{Rank: rank})
|
||||
}
|
||||
handle := UserInfoHandle{user: &oidcmiddleware.User{Roles: roles}}
|
||||
|
||||
got, err := handle.GetRoles()
|
||||
if err != nil {
|
||||
t.Fatalf("GetRoles() error = %v", err)
|
||||
}
|
||||
if got != testCase.want {
|
||||
t.Errorf("GetRoles() = %b, want %b", got, testCase.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Release permission implies review and upload. A regression here would let a
|
||||
// council member release, so the derived answer is pinned, not just the bits.
|
||||
func TestHasRolesDerivesFromRankNotEquality(t *testing.T) {
|
||||
council := UserInfoHandle{user: &oidcmiddleware.User{
|
||||
Roles: []oidcmiddleware.Role{{Rank: int(model.RoleMapCouncil)}},
|
||||
}}
|
||||
admin := UserInfoHandle{user: &oidcmiddleware.User{
|
||||
Roles: []oidcmiddleware.Role{{Rank: int(model.RoleMapAdmin)}},
|
||||
}}
|
||||
|
||||
if mayReview, _ := council.HasRoleMapfixReview(); !mayReview {
|
||||
t.Error("map council cannot review mapfixes")
|
||||
}
|
||||
if mayRelease, _ := council.HasRoleSubmissionRelease(); mayRelease {
|
||||
t.Error("map council can release submissions, which is an escalation")
|
||||
}
|
||||
if mayRelease, _ := admin.HasRoleSubmissionRelease(); !mayRelease {
|
||||
t.Error("map admin cannot release submissions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBearerAuthRejectsMissingToken(t *testing.T) {
|
||||
handler := SecurityHandler{}
|
||||
|
||||
if _, err := handler.HandleBearerAuth(context.Background(), "sessionUser", api.BearerAuth{}); err == nil {
|
||||
t.Fatal("an empty bearer token was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
// The web client must be able to read its sign-in parameters before it holds a
|
||||
// token; if this endpoint ever starts demanding one, sign-in deadlocks.
|
||||
func TestSessionConfigIsServedWithoutATokenAndProtectedRoutesAreNot(t *testing.T) {
|
||||
// inner is nil deliberately: neither path under test reaches it. The config
|
||||
// handler reads only the value below, and an unauthenticated request is
|
||||
// rejected by the security handler before any handler runs.
|
||||
svc := Service{oidc: api.OIDCConfig{
|
||||
Authority: "https://auth.example.test",
|
||||
ClientID: "maps-web",
|
||||
Audience: "maps-api",
|
||||
}}
|
||||
|
||||
// No authenticator: a unit test cannot reach a live issuer to fetch its key
|
||||
// set. Nothing here needs one — an anonymous request carries no token, so
|
||||
// it is refused before verification would be attempted. That is precisely
|
||||
// the property under test: no token, no entry.
|
||||
securityHandler := SecurityHandler{}
|
||||
|
||||
server, err := api.NewServer(&svc, securityHandler, api.WithPathPrefix("/v1"))
|
||||
if err != nil {
|
||||
t.Fatalf("NewServer() error = %v", err)
|
||||
}
|
||||
httpServer := httptest.NewServer(server)
|
||||
defer httpServer.Close()
|
||||
|
||||
response, err := http.Get(httpServer.URL + "/v1/session/config")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /v1/session/config: %v", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
t.Fatalf("GET /v1/session/config status = %d, want 200", response.StatusCode)
|
||||
}
|
||||
|
||||
var config struct {
|
||||
Authority string `json:"authority"`
|
||||
ClientID string `json:"client_id"`
|
||||
Audience string `json:"audience"`
|
||||
}
|
||||
if err := json.NewDecoder(response.Body).Decode(&config); err != nil {
|
||||
t.Fatalf("decoding config: %v", err)
|
||||
}
|
||||
// The key names are the contract oidc-client-ts reads; renaming them in the
|
||||
// spec would break sign-in without breaking the build.
|
||||
if config.Authority != "https://auth.example.test" ||
|
||||
config.ClientID != "maps-web" ||
|
||||
config.Audience != "maps-api" {
|
||||
t.Errorf("config = %+v, want the configured issuer, client id and audience", config)
|
||||
}
|
||||
|
||||
protected, err := http.Get(httpServer.URL + "/v1/session/user")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /v1/session/user: %v", err)
|
||||
}
|
||||
defer protected.Body.Close()
|
||||
|
||||
if protected.StatusCode == http.StatusOK {
|
||||
t.Error("GET /v1/session/user succeeded without a bearer token")
|
||||
}
|
||||
}
|
||||
@@ -34,15 +34,19 @@ var (
|
||||
type Service struct {
|
||||
inner *service.Service
|
||||
roblox roblox.Client
|
||||
// oidc is served verbatim to the web client so it can start a sign-in.
|
||||
oidc api.OIDCConfig
|
||||
}
|
||||
|
||||
func NewService(
|
||||
inner *service.Service,
|
||||
roblox roblox.Client,
|
||||
oidc api.OIDCConfig,
|
||||
) Service {
|
||||
return Service{
|
||||
inner: inner,
|
||||
roblox: roblox,
|
||||
oidc: oidc,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
|
||||
)
|
||||
|
||||
// SessionRoles implements getSessionRoles operation.
|
||||
@@ -17,7 +18,7 @@ func (svc *Service) SessionRoles(ctx context.Context) (*api.Roles, error) {
|
||||
return nil, ErrUserInfo
|
||||
}
|
||||
|
||||
roles, err := userInfo.GetRoles();
|
||||
roles, err := userInfo.GetRoles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -25,44 +26,71 @@ func (svc *Service) SessionRoles(ctx context.Context) (*api.Roles, error) {
|
||||
return &api.Roles{Roles: int32(roles)}, nil
|
||||
}
|
||||
|
||||
// SessionConfig implements sessionConfig operation.
|
||||
//
|
||||
// Serves the public OIDC parameters so the web client can start a sign-in
|
||||
// before it holds any token. Deliberately unauthenticated — see the spec.
|
||||
//
|
||||
// GET /session/config
|
||||
func (svc *Service) SessionConfig(ctx context.Context) (*api.OIDCConfig, error) {
|
||||
config := svc.oidc
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// SessionUser implements sessionUser operation.
|
||||
//
|
||||
// Get information about the currently logged in user.
|
||||
//
|
||||
// GET /session/roles
|
||||
// The id comes from the verified access token. Username and avatar are not
|
||||
// token claims, so they are resolved here — the only place in the service that
|
||||
// still needs a lookup to answer who the caller is.
|
||||
//
|
||||
// GET /session/user
|
||||
func (svc *Service) SessionUser(ctx context.Context) (*api.User, error) {
|
||||
userInfoHandle, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
||||
if !ok {
|
||||
return nil, ErrUserInfo
|
||||
}
|
||||
|
||||
userInfo, err := userInfoHandle.GetUserInfo();
|
||||
userID, err := userInfoHandle.GetUserID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids := []uint64{userID}
|
||||
|
||||
// A missing display name or avatar must not fail sign-in: the caller is
|
||||
// already authenticated and the id alone is enough to use the site. Both
|
||||
// lookups degrade to empty rather than erroring the request.
|
||||
username := ""
|
||||
if usernames, err := svc.inner.GetUsernames(ctx, ids); err == nil {
|
||||
username = usernames[userID]
|
||||
}
|
||||
|
||||
avatarURL := ""
|
||||
if thumbnails, err := svc.inner.GetUserAvatarThumbnails(ctx, ids, roblox.Size150x150); err == nil {
|
||||
avatarURL = thumbnails[userID]
|
||||
}
|
||||
|
||||
return &api.User{
|
||||
UserID:int64(userInfo.UserID),
|
||||
Username:userInfo.Username,
|
||||
AvatarURL:userInfo.AvatarURL,
|
||||
UserID: int64(userID),
|
||||
Username: username,
|
||||
AvatarURL: avatarURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SessionUser implements sessionUser operation.
|
||||
// SessionValidate implements sessionValidate operation.
|
||||
//
|
||||
// Get information about the currently logged in user.
|
||||
// Reports whether the caller still holds a usable identity. Reaching this
|
||||
// handler means the token already verified against the issuer, so this is a
|
||||
// cheap confirmation rather than a round trip.
|
||||
//
|
||||
// GET /session/roles
|
||||
// GET /session/validate
|
||||
func (svc *Service) SessionValidate(ctx context.Context) (bool, error) {
|
||||
userInfoHandle, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
||||
if !ok {
|
||||
return false, ErrUserInfo
|
||||
}
|
||||
|
||||
valid, err := userInfoHandle.Validate();
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return valid, nil
|
||||
return userInfoHandle.Validate()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"@tanstack/react-query": "^5.101.2",
|
||||
"date-fns": "^4.4.0",
|
||||
"lucide-react": "^1.30.0",
|
||||
"oidc-client-ts": "^3.5.0",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-router-dom": "^7.18.1",
|
||||
@@ -499,6 +500,8 @@
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"jwt-decode": ["jwt-decode@4.0.0", "", {}, "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
@@ -529,6 +532,8 @@
|
||||
|
||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||
|
||||
"oidc-client-ts": ["oidc-client-ts@3.5.0", "", { "dependencies": { "jwt-decode": "^4.0.0" } }, "sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
22
web/package-lock.json
generated
22
web/package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"@tanstack/react-query": "^5.101.2",
|
||||
"date-fns": "^4.4.0",
|
||||
"lucide-react": "^1.30.0",
|
||||
"oidc-client-ts": "^3.5.0",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-router-dom": "^7.18.1"
|
||||
@@ -3423,6 +3424,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -3566,6 +3576,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/oidc-client-ts": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.5.0.tgz",
|
||||
"integrity": "sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jwt-decode": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@tanstack/react-query": "^5.101.2",
|
||||
"date-fns": "^4.4.0",
|
||||
"lucide-react": "^1.30.0",
|
||||
"oidc-client-ts": "^3.5.0",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-router-dom": "^7.18.1"
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Suspense, lazy, useEffect } from 'react'
|
||||
import { Routes, Route, useLocation } from 'react-router-dom'
|
||||
import { Skeleton, Stack, Tooltip, ToastProvider } from '@strafesnet/ui'
|
||||
import Webpage from '@/app/_components/webpage'
|
||||
import AuthCallback from '@/app/auth/callback/page'
|
||||
|
||||
/* Routes are split. Two reasons: the review screens pull in Monaco and the
|
||||
whole audit-event machinery, which no visitor browsing the catalogue should
|
||||
@@ -75,6 +76,9 @@ function App() {
|
||||
<Route path="/review" element={<ReviewerDashboardPage />} />
|
||||
<Route path="/dashboard" element={<UserDashboardPage />} />
|
||||
<Route path="/script-review" element={<ScriptReviewPage />} />
|
||||
{/* Not lazy: this is the identity provider's return URL, so it
|
||||
must render on the very first frame of a cold load. */}
|
||||
<Route path="/auth/callback" element={<AuthCallback />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
|
||||
@@ -13,12 +13,15 @@ import {
|
||||
Gamepad2,
|
||||
LayoutDashboard,
|
||||
LogIn,
|
||||
LogOut,
|
||||
Menu,
|
||||
Plus,
|
||||
UserCog,
|
||||
} from "lucide-react"
|
||||
import { useUser } from "@/app/hooks/useUser"
|
||||
import { useRoles } from "@/app/hooks/useRoles"
|
||||
import { useAuthConfig } from "@/app/hooks/useAuthConfig"
|
||||
import { signIn, signOut } from "@/app/auth"
|
||||
import { hasAnyReviewerRole } from "@/app/ts/Roles"
|
||||
import styles from "./header.module.css"
|
||||
|
||||
@@ -50,20 +53,10 @@ export default function Header() {
|
||||
const { user, isLoggedIn, isLoading } = useUser()
|
||||
const { roles } = useRoles()
|
||||
|
||||
const authUrl =
|
||||
window.location.hostname === "maps.strafes.net"
|
||||
? "https://auth.strafes.net"
|
||||
: "https://auth.staging.strafes.net"
|
||||
|
||||
const { authority } = useAuthConfig()
|
||||
const isReviewer = hasAnyReviewerRole(roles)
|
||||
const visibleNav = navItems.filter((item) => !item.reviewerOnly || isReviewer)
|
||||
|
||||
const login = () => {
|
||||
window.location.href = `${authUrl}/oauth2/login?redirect=${encodeURIComponent(
|
||||
window.location.href,
|
||||
)}`
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar.SkipLink />
|
||||
@@ -182,8 +175,22 @@ export default function Header() {
|
||||
<Link to="/submit">Submit a map</Link>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item asChild startIcon={<UserCog size={14} />} shortcut={<ExternalLink size={12} />}>
|
||||
<a href={authUrl}>Manage account</a>
|
||||
<DropdownMenu.Item
|
||||
asChild
|
||||
disabled={!authority}
|
||||
startIcon={<UserCog size={14} />}
|
||||
shortcut={<ExternalLink size={12} />}
|
||||
>
|
||||
<a href={authority} target="_blank" rel="noreferrer noopener">
|
||||
Manage account
|
||||
</a>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
tone="danger"
|
||||
startIcon={<LogOut size={14} />}
|
||||
onSelect={() => void signOut()}
|
||||
>
|
||||
Sign out
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
@@ -191,7 +198,7 @@ export default function Header() {
|
||||
/* Outline, not solid. Red marks the page's primary action; a
|
||||
second solid accent control in the chrome competes with it
|
||||
and puts the surface over its red budget. */
|
||||
<Button size="sm" variant="outline" startIcon={<LogIn size={14} />} onClick={login}>
|
||||
<Button size="sm" variant="outline" startIcon={<LogIn size={14} />} onClick={() => void signIn()}>
|
||||
Log in
|
||||
</Button>
|
||||
)}
|
||||
|
||||
217
web/src/app/auth.ts
Normal file
217
web/src/app/auth.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { ErrorResponse, User, UserManager, WebStorageStateStore } from 'oidc-client-ts';
|
||||
|
||||
/**
|
||||
* StrafesNET OIDC sign-in, the same flow the developer portal and moderation
|
||||
* use. Replaces the old `auth.strafes.net/oauth2/login?redirect=` handoff,
|
||||
* which set an opaque session cookie the API had to exchange with the auth
|
||||
* service on every permission check.
|
||||
*/
|
||||
|
||||
interface OIDCConfig {
|
||||
authority: string;
|
||||
client_id: string;
|
||||
audience: string;
|
||||
}
|
||||
|
||||
export interface AuthenticationFailure {
|
||||
code: string;
|
||||
title: string;
|
||||
message: string;
|
||||
retryable: boolean;
|
||||
}
|
||||
|
||||
export interface AuthenticationCallbackResult {
|
||||
returnPath: string;
|
||||
failure?: AuthenticationFailure;
|
||||
}
|
||||
|
||||
let managerPromise: Promise<UserManager> | null = null;
|
||||
let renewalPromise: Promise<User | null> | null = null;
|
||||
let callbackPromise: Promise<AuthenticationCallbackResult> | null = null;
|
||||
|
||||
async function createManager(): Promise<UserManager> {
|
||||
const response = await fetch('/v1/session/config', {
|
||||
headers: { Accept: 'application/json' },
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load OIDC configuration: ${response.status}`);
|
||||
}
|
||||
const config = await response.json() as OIDCConfig;
|
||||
if (!config.authority || !config.client_id || !config.audience) {
|
||||
throw new Error('OIDC configuration is incomplete');
|
||||
}
|
||||
|
||||
const storage = new WebStorageStateStore({ store: window.sessionStorage });
|
||||
return new UserManager({
|
||||
authority: config.authority,
|
||||
client_id: config.client_id,
|
||||
redirect_uri: `${window.location.origin}/auth/callback`,
|
||||
post_logout_redirect_uri: `${window.location.origin}/`,
|
||||
response_type: 'code',
|
||||
scope: 'openid profile roles offline_access',
|
||||
extraQueryParams: { audience: config.audience },
|
||||
userStore: storage,
|
||||
stateStore: storage,
|
||||
automaticSilentRenew: true,
|
||||
revokeTokensOnSignout: true,
|
||||
monitorSession: false,
|
||||
});
|
||||
}
|
||||
|
||||
function manager(): Promise<UserManager> {
|
||||
if (!managerPromise) {
|
||||
managerPromise = createManager().catch(error => {
|
||||
managerPromise = null;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
return managerPromise;
|
||||
}
|
||||
|
||||
/** Only same-origin paths are safe to return to after a redirect. */
|
||||
function safeReturnPath(value: unknown): string {
|
||||
if (typeof value !== 'string' || !value.startsWith('/') || value.startsWith('//')) {
|
||||
return '/';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function authenticationCallbackFailure(error: unknown): AuthenticationCallbackResult {
|
||||
const response = error instanceof ErrorResponse ? error : null;
|
||||
const code = response?.error || 'callback_failed';
|
||||
|
||||
const knownFailures: Record<string, Omit<AuthenticationFailure, 'code'>> = {
|
||||
access_denied: {
|
||||
title: 'Sign-in was canceled',
|
||||
message: 'Access was not granted, so you are still signed out.',
|
||||
retryable: true,
|
||||
},
|
||||
login_required: {
|
||||
title: 'Sign-in is required',
|
||||
message: 'Your StrafesNET session is no longer available. Sign in again to continue.',
|
||||
retryable: true,
|
||||
},
|
||||
consent_required: {
|
||||
title: 'Permission is required',
|
||||
message: 'Maps was not granted the permissions it needs to sign you in.',
|
||||
retryable: true,
|
||||
},
|
||||
interaction_required: {
|
||||
title: 'Confirmation is required',
|
||||
message: 'StrafesNET needs additional confirmation before maps can sign you in.',
|
||||
retryable: true,
|
||||
},
|
||||
server_error: {
|
||||
title: 'Authentication is unavailable',
|
||||
message: 'The authentication service could not complete the request. Please try again.',
|
||||
retryable: true,
|
||||
},
|
||||
temporarily_unavailable: {
|
||||
title: 'Authentication is unavailable',
|
||||
message: 'The authentication service is temporarily unavailable. Please try again.',
|
||||
retryable: true,
|
||||
},
|
||||
invalid_request: {
|
||||
title: 'Invalid sign-in request',
|
||||
message: 'The sign-in request could not be accepted. If this continues, contact a StrafesNET administrator.',
|
||||
retryable: false,
|
||||
},
|
||||
invalid_client: {
|
||||
title: 'Invalid sign-in configuration',
|
||||
message: 'Maps is not configured correctly for sign-in. Contact a StrafesNET administrator.',
|
||||
retryable: false,
|
||||
},
|
||||
unauthorized_client: {
|
||||
title: 'Sign-in is not authorized',
|
||||
message: 'Maps is not authorized to use this sign-in flow. Contact a StrafesNET administrator.',
|
||||
retryable: false,
|
||||
},
|
||||
unsupported_response_type: {
|
||||
title: 'Unsupported sign-in request',
|
||||
message: 'Maps requested an unsupported sign-in flow. Contact a StrafesNET administrator.',
|
||||
retryable: false,
|
||||
},
|
||||
invalid_scope: {
|
||||
title: 'Invalid permission request',
|
||||
message: 'Maps requested permissions that the authentication service could not grant.',
|
||||
retryable: false,
|
||||
},
|
||||
};
|
||||
const failure = knownFailures[code] ?? {
|
||||
title: 'Sign-in could not be completed',
|
||||
message: 'The authentication response could not be verified. Start the sign-in again.',
|
||||
retryable: true,
|
||||
};
|
||||
|
||||
return {
|
||||
returnPath: safeReturnPath(response?.state),
|
||||
failure: { code, ...failure },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the redirect if this load is the callback. Returns null on every
|
||||
* other URL, so the app can call it unconditionally at boot.
|
||||
*/
|
||||
export async function initializeAuthentication(): Promise<AuthenticationCallbackResult | null> {
|
||||
if (window.location.pathname !== '/auth/callback') {
|
||||
return null;
|
||||
}
|
||||
if (!callbackPromise) {
|
||||
callbackPromise = (async () => {
|
||||
try {
|
||||
const user = await (await manager()).signinRedirectCallback();
|
||||
return { returnPath: safeReturnPath(user.state) };
|
||||
} catch (error) {
|
||||
return authenticationCallbackFailure(error);
|
||||
}
|
||||
})();
|
||||
}
|
||||
return callbackPromise;
|
||||
}
|
||||
|
||||
export async function signIn(returnPath?: string): Promise<void> {
|
||||
const destination = returnPath ??
|
||||
`${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
await (await manager()).signinRedirect({ state: safeReturnPath(destination) });
|
||||
}
|
||||
|
||||
export async function signOut(): Promise<void> {
|
||||
const auth = await manager();
|
||||
const user = await auth.getUser();
|
||||
if (!user) {
|
||||
window.location.assign('/');
|
||||
return;
|
||||
}
|
||||
await auth.signoutRedirect({ id_token_hint: user.id_token });
|
||||
}
|
||||
|
||||
/** The current user, silently renewing an expired token once before failing. */
|
||||
async function currentUser(): Promise<User | null> {
|
||||
const auth = await manager();
|
||||
const user = await auth.getUser();
|
||||
if (!user || !user.expired) {
|
||||
return user;
|
||||
}
|
||||
if (!renewalPromise) {
|
||||
renewalPromise = auth.signinSilent()
|
||||
.catch(async () => {
|
||||
await auth.removeUser();
|
||||
return null;
|
||||
})
|
||||
.finally(() => {
|
||||
renewalPromise = null;
|
||||
});
|
||||
}
|
||||
return renewalPromise;
|
||||
}
|
||||
|
||||
export async function authenticatedFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {
|
||||
const user = await currentUser();
|
||||
const headers = new Headers(init.headers);
|
||||
if (user?.access_token) {
|
||||
headers.set('Authorization', `Bearer ${user.access_token}`);
|
||||
}
|
||||
return fetch(input, { ...init, headers });
|
||||
}
|
||||
86
web/src/app/auth/callback/page.tsx
Normal file
86
web/src/app/auth/callback/page.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Button, EmptyState, Spinner, Stack } from "@strafesnet/ui";
|
||||
import { LogIn, ShieldAlert } from "lucide-react";
|
||||
import Webpage from "@/app/_components/webpage";
|
||||
import { useTitle } from "@/app/hooks/useTitle";
|
||||
import {
|
||||
initializeAuthentication,
|
||||
signIn,
|
||||
type AuthenticationFailure,
|
||||
} from "@/app/auth";
|
||||
|
||||
/**
|
||||
* Where the identity provider returns after sign-in.
|
||||
*
|
||||
* The authorization code is exchanged here, then the visitor is sent back to
|
||||
* whatever page started the flow — `replace`, so the callback URL with its
|
||||
* one-time code never lands in history and cannot be re-entered by going back.
|
||||
*/
|
||||
export default function AuthCallback() {
|
||||
useTitle("Signing in");
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const [failure, setFailure] = useState<AuthenticationFailure | null>(null);
|
||||
const [retrying, setRetrying] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
void (async () => {
|
||||
const result = await initializeAuthentication();
|
||||
if (cancelled || !result) return;
|
||||
|
||||
if (result.failure) {
|
||||
setFailure(result.failure);
|
||||
return;
|
||||
}
|
||||
|
||||
// The identity changed, so anything keyed to the old one is stale —
|
||||
// most visibly the header, which would otherwise still say Log in.
|
||||
await queryClient.invalidateQueries({ queryKey: ["user"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["session", "roles"] });
|
||||
navigate(result.returnPath, { replace: true });
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [navigate, queryClient]);
|
||||
|
||||
if (!failure) {
|
||||
return (
|
||||
<Webpage width="narrow">
|
||||
<Stack gap={5} align="center" aria-busy="true">
|
||||
<Spinner size="lg" />
|
||||
<span>Completing sign-in…</span>
|
||||
</Stack>
|
||||
</Webpage>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Webpage width="narrow">
|
||||
<EmptyState
|
||||
icon={<ShieldAlert />}
|
||||
title={failure.title}
|
||||
description={failure.message}
|
||||
action={
|
||||
failure.retryable ? (
|
||||
<Button
|
||||
startIcon={<LogIn size={15} />}
|
||||
loading={retrying}
|
||||
onClick={() => {
|
||||
setRetrying(true);
|
||||
void signIn("/").catch(() => setRetrying(false));
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</Webpage>
|
||||
);
|
||||
}
|
||||
37
web/src/app/hooks/useAuthConfig.ts
Normal file
37
web/src/app/hooks/useAuthConfig.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
export interface AuthConfig {
|
||||
authority: string;
|
||||
client_id: string;
|
||||
audience: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The public OIDC parameters, served by `/v1/session/config`.
|
||||
*
|
||||
* The account-management link needs the issuer's address, which used to be
|
||||
* guessed from `window.location.hostname` in the header — so a new deployment
|
||||
* hostname silently pointed people at the wrong environment. The server knows
|
||||
* which issuer it actually verifies against, so it is the one that answers.
|
||||
*
|
||||
* Unauthenticated and effectively immutable for the life of the page, hence no
|
||||
* refetching.
|
||||
*/
|
||||
export function useAuthConfig() {
|
||||
const { data } = useQuery({
|
||||
queryKey: ['session', 'config'],
|
||||
queryFn: async (): Promise<AuthConfig | null> => {
|
||||
const response = await fetch('/v1/session/config', {
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
return await response.json() as AuthConfig;
|
||||
},
|
||||
staleTime: Infinity,
|
||||
gcTime: Infinity,
|
||||
});
|
||||
|
||||
return { authority: data?.authority };
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { RolesConstants, type Roles } from '@/app/ts/Roles';
|
||||
import { useUser } from '@/app/hooks/useUser';
|
||||
import { authenticatedFetch } from '@/app/auth';
|
||||
|
||||
/* Roles now ride in the access token's claims, so this is a read of the
|
||||
caller's own token rather than a session lookup on the auth service. */
|
||||
async function fetchRoles(): Promise<Roles> {
|
||||
const response = await fetch('/v1/session/roles');
|
||||
const response = await authenticatedFetch('/v1/session/roles');
|
||||
if (!response.ok) {
|
||||
return RolesConstants.Empty;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { UserInfo } from '@/app/ts/User';
|
||||
import { authenticatedFetch } from '@/app/auth';
|
||||
|
||||
/**
|
||||
* Identity comes from the OIDC access token now rather than a session cookie,
|
||||
* so the request carries a bearer header and a 401 just means signed out.
|
||||
*/
|
||||
async function fetchUser(): Promise<UserInfo | null> {
|
||||
try {
|
||||
const response = await fetch('/v1/session/user');
|
||||
const response = await authenticatedFetch('/v1/session/user');
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user