[feature] More consistent API error handling (#637)
* update templates * start reworking api error handling * update template * return AP status at web endpoint if negotiated * start making api error handling much more consistent * update account endpoints to new error handling * use new api error handling in admin endpoints * go fmt ./... * use api error logic in app * use generic error handling in auth * don't export generic error handler * don't defer clearing session * user nicer error handling on oidc callback handler * tidy up the sign in handler * tidy up the token handler * use nicer error handling in blocksget * auth emojis endpoint * fix up remaining api endpoints * fix whoopsie during login flow * regenerate swagger docs * change http error logging to debug
This commit is contained in:
parent
91c0ed863a
commit
1ede54ddf6
|
@ -165,7 +165,7 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// build client api modules
|
// build client api modules
|
||||||
authModule := auth.New(dbService, oauthServer, idp)
|
authModule := auth.New(dbService, oauthServer, idp, processor)
|
||||||
accountModule := account.New(processor)
|
accountModule := account.New(processor)
|
||||||
instanceModule := instance.New(processor)
|
instanceModule := instance.New(processor)
|
||||||
appsModule := app.New(processor)
|
appsModule := app.New(processor)
|
||||||
|
|
|
@ -108,7 +108,7 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// build client api modules
|
// build client api modules
|
||||||
authModule := auth.New(dbService, oauthServer, idp)
|
authModule := auth.New(dbService, oauthServer, idp, processor)
|
||||||
accountModule := account.New(processor)
|
accountModule := account.New(processor)
|
||||||
instanceModule := instance.New(processor)
|
instanceModule := instance.New(processor)
|
||||||
appsModule := app.New(processor)
|
appsModule := app.New(processor)
|
||||||
|
|
|
@ -692,7 +692,7 @@ definitions:
|
||||||
text_url:
|
text_url:
|
||||||
description: |-
|
description: |-
|
||||||
A shorter URL for the attachment.
|
A shorter URL for the attachment.
|
||||||
Not currently used.
|
In our case, we just give the URL again since we don't create smaller URLs.
|
||||||
type: string
|
type: string
|
||||||
x-go-name: TextURL
|
x-go-name: TextURL
|
||||||
type:
|
type:
|
||||||
|
@ -1894,8 +1894,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
"500":
|
"500":
|
||||||
description: internal error
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Application:
|
- OAuth2 Application:
|
||||||
- write:accounts
|
- write:accounts
|
||||||
|
@ -1924,6 +1926,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:accounts
|
- read:accounts
|
||||||
|
@ -1952,6 +1958,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:blocks
|
- write:blocks
|
||||||
|
@ -1999,6 +2009,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:follows
|
- write:follows
|
||||||
|
@ -2029,6 +2043,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:accounts
|
- read:accounts
|
||||||
|
@ -2059,6 +2077,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:accounts
|
- read:accounts
|
||||||
|
@ -2134,6 +2156,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:accounts
|
- read:accounts
|
||||||
|
@ -2162,6 +2188,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:blocks
|
- write:blocks
|
||||||
|
@ -2190,6 +2220,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:follows
|
- write:follows
|
||||||
|
@ -2215,6 +2249,12 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
|
"404":
|
||||||
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:accounts
|
- write:accounts
|
||||||
|
@ -2247,6 +2287,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:accounts
|
- read:accounts
|
||||||
|
@ -2313,6 +2357,12 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
|
"404":
|
||||||
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:accounts
|
- write:accounts
|
||||||
|
@ -2335,6 +2385,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:accounts
|
- read:accounts
|
||||||
|
@ -2372,6 +2426,12 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"403":
|
"403":
|
||||||
description: forbidden
|
description: forbidden
|
||||||
|
"404":
|
||||||
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- admin
|
- admin
|
||||||
|
@ -2406,10 +2466,18 @@ paths:
|
||||||
$ref: '#/definitions/emoji'
|
$ref: '#/definitions/emoji'
|
||||||
"400":
|
"400":
|
||||||
description: bad request
|
description: bad request
|
||||||
|
"401":
|
||||||
|
description: unauthorized
|
||||||
"403":
|
"403":
|
||||||
description: forbidden
|
description: forbidden
|
||||||
|
"404":
|
||||||
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
"409":
|
"409":
|
||||||
description: conflict -- domain/shortcode combo for emoji already exists
|
description: conflict -- domain/shortcode combo for emoji already exists
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- admin
|
- admin
|
||||||
|
@ -2439,10 +2507,16 @@ paths:
|
||||||
type: array
|
type: array
|
||||||
"400":
|
"400":
|
||||||
description: bad request
|
description: bad request
|
||||||
|
"401":
|
||||||
|
description: unauthorized
|
||||||
"403":
|
"403":
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- admin
|
- admin
|
||||||
|
@ -2511,8 +2585,16 @@ paths:
|
||||||
$ref: '#/definitions/domainBlock'
|
$ref: '#/definitions/domainBlock'
|
||||||
"400":
|
"400":
|
||||||
description: bad request
|
description: bad request
|
||||||
|
"401":
|
||||||
|
description: unauthorized
|
||||||
"403":
|
"403":
|
||||||
description: forbidden
|
description: forbidden
|
||||||
|
"404":
|
||||||
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- admin
|
- admin
|
||||||
|
@ -2537,10 +2619,16 @@ paths:
|
||||||
$ref: '#/definitions/domainBlock'
|
$ref: '#/definitions/domainBlock'
|
||||||
"400":
|
"400":
|
||||||
description: bad request
|
description: bad request
|
||||||
|
"401":
|
||||||
|
description: unauthorized
|
||||||
"403":
|
"403":
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- admin
|
- admin
|
||||||
|
@ -2564,10 +2652,16 @@ paths:
|
||||||
$ref: '#/definitions/domainBlock'
|
$ref: '#/definitions/domainBlock'
|
||||||
"400":
|
"400":
|
||||||
description: bad request
|
description: bad request
|
||||||
|
"401":
|
||||||
|
description: unauthorized
|
||||||
"403":
|
"403":
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- admin
|
- admin
|
||||||
|
@ -2599,8 +2693,16 @@ paths:
|
||||||
asynchronously after the request completes.
|
asynchronously after the request completes.
|
||||||
"400":
|
"400":
|
||||||
description: bad request
|
description: bad request
|
||||||
|
"401":
|
||||||
|
description: unauthorized
|
||||||
"403":
|
"403":
|
||||||
description: forbidden
|
description: forbidden
|
||||||
|
"404":
|
||||||
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- admin
|
- admin
|
||||||
|
@ -2660,10 +2762,14 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"422":
|
"403":
|
||||||
description: unprocessable
|
description: forbidden
|
||||||
|
"404":
|
||||||
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
"500":
|
"500":
|
||||||
description: internal error
|
description: internal server error
|
||||||
summary: Register a new application on this instance.
|
summary: Register a new application on this instance.
|
||||||
tags:
|
tags:
|
||||||
- apps
|
- apps
|
||||||
|
@ -2714,6 +2820,10 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:blocks
|
- read:blocks
|
||||||
|
@ -2753,10 +2863,12 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"403":
|
|
||||||
description: forbidden
|
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:follows
|
- read:follows
|
||||||
|
@ -2785,10 +2897,10 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"403":
|
|
||||||
description: forbidden
|
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
"500":
|
"500":
|
||||||
description: internal server error
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
|
@ -2817,10 +2929,10 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"403":
|
|
||||||
description: forbidden
|
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
"500":
|
"500":
|
||||||
description: internal server error
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
|
@ -2843,6 +2955,8 @@ paths:
|
||||||
description: Instance information.
|
description: Instance information.
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/instance'
|
$ref: '#/definitions/instance'
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
"500":
|
"500":
|
||||||
description: internal error
|
description: internal error
|
||||||
summary: View instance information.
|
summary: View instance information.
|
||||||
|
@ -2909,6 +3023,14 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
|
"403":
|
||||||
|
description: forbidden
|
||||||
|
"404":
|
||||||
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- admin
|
- admin
|
||||||
|
@ -2952,10 +3074,10 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"403":
|
|
||||||
description: forbidden
|
|
||||||
"422":
|
"422":
|
||||||
description: unprocessable
|
description: unprocessable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:media
|
- write:media
|
||||||
|
@ -2982,10 +3104,12 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"403":
|
"404":
|
||||||
description: forbidden
|
description: not found
|
||||||
"422":
|
"406":
|
||||||
description: unprocessable
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:media
|
- read:media
|
||||||
|
@ -3036,10 +3160,12 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"403":
|
"404":
|
||||||
description: forbidden
|
description: not found
|
||||||
"422":
|
"406":
|
||||||
description: unprocessable
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:media
|
- write:media
|
||||||
|
@ -3141,6 +3267,12 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
|
"404":
|
||||||
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:search
|
- read:search
|
||||||
|
@ -3226,10 +3358,14 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
|
"403":
|
||||||
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
"500":
|
"500":
|
||||||
description: internal error
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:statuses
|
- write:statuses
|
||||||
|
@ -3263,6 +3399,10 @@ paths:
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:statuses
|
- write:statuses
|
||||||
|
@ -3288,10 +3428,14 @@ paths:
|
||||||
description: bad request
|
description: bad request
|
||||||
"401":
|
"401":
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
|
"403":
|
||||||
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
"500":
|
"500":
|
||||||
description: internal error
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:statuses
|
- read:statuses
|
||||||
|
@ -3324,6 +3468,10 @@ paths:
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:statuses
|
- read:statuses
|
||||||
|
@ -3354,6 +3502,10 @@ paths:
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:statuses
|
- write:statuses
|
||||||
|
@ -3386,6 +3538,10 @@ paths:
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- read:accounts
|
- read:accounts
|
||||||
|
@ -3419,6 +3575,10 @@ paths:
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:statuses
|
- write:statuses
|
||||||
|
@ -3481,6 +3641,10 @@ paths:
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:statuses
|
- write:statuses
|
||||||
|
@ -3511,6 +3675,10 @@ paths:
|
||||||
description: forbidden
|
description: forbidden
|
||||||
"404":
|
"404":
|
||||||
description: not found
|
description: not found
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:statuses
|
- write:statuses
|
||||||
|
@ -3778,6 +3946,8 @@ paths:
|
||||||
description: unauthorized
|
description: unauthorized
|
||||||
"403":
|
"403":
|
||||||
description: forbidden
|
description: forbidden
|
||||||
|
"406":
|
||||||
|
description: not acceptable
|
||||||
"500":
|
"500":
|
||||||
description: internal error
|
description: internal error
|
||||||
security:
|
security:
|
||||||
|
|
|
@ -23,12 +23,11 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||||
)
|
)
|
||||||
|
@ -61,58 +60,51 @@ import (
|
||||||
// description: "An OAuth2 access token for the newly-created account."
|
// description: "An OAuth2 access token for the newly-created account."
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/oauthToken"
|
// "$ref": "#/definitions/oauthToken"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
// '500':
|
// '500':
|
||||||
// description: internal error
|
// description: internal server error
|
||||||
func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "accountCreatePOSTHandler")
|
|
||||||
authed, err := oauth.Authed(c, true, true, false, false)
|
authed, err := oauth.Authed(c, true, true, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Trace("parsing request form")
|
|
||||||
form := &model.AccountCreateRequest{}
|
form := &model.AccountCreateRequest{}
|
||||||
if err := c.ShouldBind(form); err != nil || form == nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
l.Debugf("could not parse form from request: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "missing one or more required form values"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Tracef("validating form %+v", form)
|
|
||||||
if err := validateCreateAccount(form); err != nil {
|
if err := validateCreateAccount(form); err != nil {
|
||||||
l.Debugf("error validating form: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
clientIP := c.ClientIP()
|
clientIP := c.ClientIP()
|
||||||
l.Tracef("attempting to parse client ip address %s", clientIP)
|
|
||||||
signUpIP := net.ParseIP(clientIP)
|
signUpIP := net.ParseIP(clientIP)
|
||||||
if signUpIP == nil {
|
if signUpIP == nil {
|
||||||
l.Debugf("error validating sign up ip address %s", clientIP)
|
err := errors.New("ip address could not be parsed from request")
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "ip address could not be parsed from request"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form.IP = signUpIP
|
form.IP = signUpIP
|
||||||
|
|
||||||
ti, err := m.processor.AccountCreate(c.Request.Context(), authed, form)
|
ti, errWithCode := m.processor.AccountCreate(c.Request.Context(), authed, form)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Errorf("internal server error while creating new account: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +114,10 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
||||||
// validateCreateAccount checks through all the necessary prerequisites for creating a new account,
|
// validateCreateAccount checks through all the necessary prerequisites for creating a new account,
|
||||||
// according to the provided account create request. If the account isn't eligible, an error will be returned.
|
// according to the provided account create request. If the account isn't eligible, an error will be returned.
|
||||||
func validateCreateAccount(form *model.AccountCreateRequest) error {
|
func validateCreateAccount(form *model.AccountCreateRequest) error {
|
||||||
|
if form == nil {
|
||||||
|
return errors.New("form was nil")
|
||||||
|
}
|
||||||
|
|
||||||
if !config.GetAccountsRegistrationOpen() {
|
if !config.GetAccountsRegistrationOpen() {
|
||||||
return errors.New("registration is not open for this server")
|
return errors.New("registration is not open for this server")
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,13 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,32 +58,35 @@ import (
|
||||||
// description: bad request
|
// description: bad request
|
||||||
// '401':
|
// '401':
|
||||||
// description: unauthorized
|
// description: unauthorized
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountDeletePOSTHandler(c *gin.Context) {
|
func (m *Module) AccountDeletePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "AccountDeletePOSTHandler")
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Tracef("retrieved account %+v", authed.Account.ID)
|
|
||||||
|
|
||||||
form := &model.AccountDeleteRequest{}
|
form := &model.AccountDeleteRequest{}
|
||||||
if err := c.ShouldBind(&form); err != nil {
|
if err := c.ShouldBind(&form); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Password == "" {
|
if form.Password == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no password provided in account delete request"})
|
err = errors.New("no password provided in account delete request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form.DeleteOriginID = authed.Account.ID
|
form.DeleteOriginID = authed.Account.ID
|
||||||
|
|
||||||
if errWithCode := m.processor.AccountDeleteLocal(c.Request.Context(), authed, form); errWithCode != nil {
|
if errWithCode := m.processor.AccountDeleteLocal(c.Request.Context(), authed, form); errWithCode != nil {
|
||||||
l.Debugf("could not delete account: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,34 +54,38 @@ import (
|
||||||
// '200':
|
// '200':
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/account"
|
// "$ref": "#/definitions/account"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountGETHandler(c *gin.Context) {
|
func (m *Module) AccountGETHandler(c *gin.Context) {
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
err := errors.New("no account id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
acctInfo, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID)
|
acctInfo, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
logrus.Debug(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,15 +19,15 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -98,33 +98,54 @@ import (
|
||||||
// description: "The newly updated account."
|
// description: "The newly updated account."
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/account"
|
// "$ref": "#/definitions/account"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) {
|
func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "accountUpdateCredentialsPATCHHandler")
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Tracef("retrieved account %+v", authed.Account.ID)
|
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form, err := parseUpdateAccountForm(c)
|
form, err := parseUpdateAccountForm(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if everything on the form is nil, then nothing has been set and we shouldn't continue
|
acctSensitive, errWithCode := m.processor.AccountUpdate(c.Request.Context(), authed, form)
|
||||||
if form.Discoverable == nil &&
|
if errWithCode != nil {
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, acctSensitive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUpdateAccountForm(c *gin.Context) (*model.UpdateCredentialsRequest, error) {
|
||||||
|
form := &model.UpdateCredentialsRequest{
|
||||||
|
Source: &model.UpdateSource{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBind(&form); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse form from request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if form == nil ||
|
||||||
|
(form.Discoverable == nil &&
|
||||||
form.Bot == nil &&
|
form.Bot == nil &&
|
||||||
form.DisplayName == nil &&
|
form.DisplayName == nil &&
|
||||||
form.Note == nil &&
|
form.Note == nil &&
|
||||||
|
@ -134,30 +155,8 @@ func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) {
|
||||||
form.Source.Privacy == nil &&
|
form.Source.Privacy == nil &&
|
||||||
form.Source.Sensitive == nil &&
|
form.Source.Sensitive == nil &&
|
||||||
form.Source.Language == nil &&
|
form.Source.Language == nil &&
|
||||||
form.FieldsAttributes == nil {
|
form.FieldsAttributes == nil) {
|
||||||
l.Debugf("could not parse form from request")
|
return nil, errors.New("empty form submitted")
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "empty form submitted"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
acctSensitive, err := m.processor.AccountUpdate(c.Request.Context(), authed, form)
|
|
||||||
if err != nil {
|
|
||||||
l.Debugf("could not update account: %s", err)
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Tracef("conversion successful, returning OK and apisensitive account %+v", acctSensitive)
|
|
||||||
c.JSON(http.StatusOK, acctSensitive)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUpdateAccountForm(c *gin.Context) (*model.UpdateCredentialsRequest, error) {
|
|
||||||
// parse main fields from request
|
|
||||||
form := &model.UpdateCredentialsRequest{
|
|
||||||
Source: &model.UpdateSource{},
|
|
||||||
}
|
|
||||||
if err := c.ShouldBind(&form); err != nil || form == nil {
|
|
||||||
return nil, fmt.Errorf("could not parse form from request: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse source field-by-field
|
// parse source field-by-field
|
||||||
|
|
|
@ -26,7 +26,6 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/account"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/account"
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
@ -65,7 +64,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandler()
|
||||||
|
|
||||||
// check the response
|
// check the response
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
assert.NoError(suite.T(), err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// unmarshal the returned account
|
// unmarshal the returned account
|
||||||
apimodelAccount := &apimodel.Account{}
|
apimodelAccount := &apimodel.Account{}
|
||||||
|
@ -104,7 +103,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUnl
|
||||||
|
|
||||||
// check the response
|
// check the response
|
||||||
b1, err := ioutil.ReadAll(result1.Body)
|
b1, err := ioutil.ReadAll(result1.Body)
|
||||||
assert.NoError(suite.T(), err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// unmarshal the returned account
|
// unmarshal the returned account
|
||||||
apimodelAccount1 := &apimodel.Account{}
|
apimodelAccount1 := &apimodel.Account{}
|
||||||
|
@ -185,7 +184,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerGet
|
||||||
|
|
||||||
// check the response
|
// check the response
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
assert.NoError(suite.T(), err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// unmarshal the returned account
|
// unmarshal the returned account
|
||||||
apimodelAccount := &apimodel.Account{}
|
apimodelAccount := &apimodel.Account{}
|
||||||
|
@ -227,7 +226,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerTwo
|
||||||
|
|
||||||
// check the response
|
// check the response
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
assert.NoError(suite.T(), err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// unmarshal the returned account
|
// unmarshal the returned account
|
||||||
apimodelAccount := &apimodel.Account{}
|
apimodelAccount := &apimodel.Account{}
|
||||||
|
@ -271,7 +270,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerWit
|
||||||
|
|
||||||
// check the response
|
// check the response
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
assert.NoError(suite.T(), err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// unmarshal the returned account
|
// unmarshal the returned account
|
||||||
apimodelAccount := &apimodel.Account{}
|
apimodelAccount := &apimodel.Account{}
|
||||||
|
@ -313,8 +312,8 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerEmp
|
||||||
|
|
||||||
// check the response
|
// check the response
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
assert.NoError(suite.T(), err)
|
suite.NoError(err)
|
||||||
suite.Equal(`{"error":"empty form submitted"}`, string(b))
|
suite.Equal(`{"error":"Bad Request: empty form submitted"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateSource() {
|
func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpdateSource() {
|
||||||
|
@ -348,7 +347,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd
|
||||||
|
|
||||||
// check the response
|
// check the response
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
assert.NoError(suite.T(), err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// unmarshal the returned account
|
// unmarshal the returned account
|
||||||
apimodelAccount := &apimodel.Account{}
|
apimodelAccount := &apimodel.Account{}
|
||||||
|
|
|
@ -21,10 +21,9 @@ package account
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,30 +46,31 @@ import (
|
||||||
// '200':
|
// '200':
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/account"
|
// "$ref": "#/definitions/account"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountVerifyGETHandler(c *gin.Context) {
|
func (m *Module) AccountVerifyGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "accountVerifyGETHandler")
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
authed, err := oauth.Authed(c, true, false, false, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
acctSensitive, err := m.processor.AccountGet(c.Request.Context(), authed, authed.Account.ID)
|
acctSensitive, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, authed.Account.ID)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error getting account from processor: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,33 +56,38 @@ import (
|
||||||
// description: Your relationship to this account.
|
// description: Your relationship to this account.
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/accountRelationship"
|
// "$ref": "#/definitions/accountRelationship"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountBlockPOSTHandler(c *gin.Context) {
|
func (m *Module) AccountBlockPOSTHandler(c *gin.Context) {
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
err := errors.New("no account id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
relationship, errWithCode := m.processor.AccountBlockCreate(c.Request.Context(), authed, targetAcctID)
|
relationship, errWithCode := m.processor.AccountBlockCreate(c.Request.Context(), authed, targetAcctID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,13 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,39 +77,45 @@ import (
|
||||||
// description: Your relationship to this account.
|
// description: Your relationship to this account.
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/accountRelationship"
|
// "$ref": "#/definitions/accountRelationship"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountFollowPOSTHandler(c *gin.Context) {
|
func (m *Module) AccountFollowPOSTHandler(c *gin.Context) {
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
err := errors.New("no account id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form := &model.AccountFollowRequest{}
|
form := &model.AccountFollowRequest{}
|
||||||
if err := c.ShouldBind(form); err != nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
form.ID = targetAcctID
|
form.ID = targetAcctID
|
||||||
|
|
||||||
relationship, errWithCode := m.processor.AccountFollowCreate(c.Request.Context(), authed, form)
|
relationship, errWithCode := m.processor.AccountFollowCreate(c.Request.Context(), authed, form)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,33 +58,38 @@ import (
|
||||||
// type: array
|
// type: array
|
||||||
// items:
|
// items:
|
||||||
// "$ref": "#/definitions/account"
|
// "$ref": "#/definitions/account"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountFollowersGETHandler(c *gin.Context) {
|
func (m *Module) AccountFollowersGETHandler(c *gin.Context) {
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
err := errors.New("no account id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
followers, errWithCode := m.processor.AccountFollowersGet(c.Request.Context(), authed, targetAcctID)
|
followers, errWithCode := m.processor.AccountFollowersGet(c.Request.Context(), authed, targetAcctID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,33 +58,38 @@ import (
|
||||||
// type: array
|
// type: array
|
||||||
// items:
|
// items:
|
||||||
// "$ref": "#/definitions/account"
|
// "$ref": "#/definitions/account"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountFollowingGETHandler(c *gin.Context) {
|
func (m *Module) AccountFollowingGETHandler(c *gin.Context) {
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
err := errors.New("no account id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
following, errWithCode := m.processor.AccountFollowingGet(c.Request.Context(), authed, targetAcctID)
|
following, errWithCode := m.processor.AccountFollowingGet(c.Request.Context(), authed, targetAcctID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,24 +43,25 @@ import (
|
||||||
// type: array
|
// type: array
|
||||||
// items:
|
// items:
|
||||||
// "$ref": "#/definitions/accountRelationship"
|
// "$ref": "#/definitions/accountRelationship"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {
|
func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "AccountRelationshipsGETHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error authing: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,8 +70,8 @@ func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {
|
||||||
// check fallback -- let's be generous and see if maybe it's just set as 'id'?
|
// check fallback -- let's be generous and see if maybe it's just set as 'id'?
|
||||||
id := c.Query("id")
|
id := c.Query("id")
|
||||||
if id == "" {
|
if id == "" {
|
||||||
l.Debug("no account id specified in query")
|
err = errors.New("no account id(s) specified in query")
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
targetAccountIDs = append(targetAccountIDs, id)
|
targetAccountIDs = append(targetAccountIDs, id)
|
||||||
|
@ -80,8 +81,8 @@ func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
for _, targetAccountID := range targetAccountIDs {
|
for _, targetAccountID := range targetAccountIDs {
|
||||||
r, errWithCode := m.processor.AccountRelationshipGet(c.Request.Context(), authed, targetAccountID)
|
r, errWithCode := m.processor.AccountRelationshipGet(c.Request.Context(), authed, targetAccountID)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
relationships = append(relationships, *r)
|
relationships = append(relationships, *r)
|
||||||
|
|
|
@ -19,13 +19,14 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -110,31 +111,32 @@ import (
|
||||||
// type: array
|
// type: array
|
||||||
// items:
|
// items:
|
||||||
// "$ref": "#/definitions/status"
|
// "$ref": "#/definitions/status"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "AccountStatusesGETHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, false, false, false, false)
|
authed, err := oauth.Authed(c, false, false, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error authing: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
l.Debug("no account id specified in query")
|
err := errors.New("no account id specified")
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,8 +145,8 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
if limitString != "" {
|
if limitString != "" {
|
||||||
i, err := strconv.ParseInt(limitString, 10, 64)
|
i, err := strconv.ParseInt(limitString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing limit string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", LimitKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit = int(i)
|
limit = int(i)
|
||||||
|
@ -155,8 +157,8 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
if excludeRepliesString != "" {
|
if excludeRepliesString != "" {
|
||||||
i, err := strconv.ParseBool(excludeRepliesString)
|
i, err := strconv.ParseBool(excludeRepliesString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing exclude replies string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", ExcludeRepliesKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse exclude replies query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
excludeReplies = i
|
excludeReplies = i
|
||||||
|
@ -167,8 +169,8 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
if excludeReblogsString != "" {
|
if excludeReblogsString != "" {
|
||||||
i, err := strconv.ParseBool(excludeReblogsString)
|
i, err := strconv.ParseBool(excludeReblogsString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing exclude reblogs string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", ExcludeReblogsKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse exclude reblogs query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
excludeReblogs = i
|
excludeReblogs = i
|
||||||
|
@ -191,8 +193,8 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
if pinnedString != "" {
|
if pinnedString != "" {
|
||||||
i, err := strconv.ParseBool(pinnedString)
|
i, err := strconv.ParseBool(pinnedString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing pinned string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", PinnedKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse pinned query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pinnedOnly = i
|
pinnedOnly = i
|
||||||
|
@ -203,8 +205,8 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
if mediaOnlyString != "" {
|
if mediaOnlyString != "" {
|
||||||
i, err := strconv.ParseBool(mediaOnlyString)
|
i, err := strconv.ParseBool(mediaOnlyString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing media only string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", OnlyMediaKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse media only query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mediaOnly = i
|
mediaOnly = i
|
||||||
|
@ -215,8 +217,8 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
if publicOnlyString != "" {
|
if publicOnlyString != "" {
|
||||||
i, err := strconv.ParseBool(publicOnlyString)
|
i, err := strconv.ParseBool(publicOnlyString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing public only string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", OnlyPublicKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse public only query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
publicOnly = i
|
publicOnly = i
|
||||||
|
@ -224,8 +226,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
resp, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
|
resp, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error from processor account statuses get: %s", errWithCode)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,33 +56,38 @@ import (
|
||||||
// description: Your relationship to this account.
|
// description: Your relationship to this account.
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/accountRelationship"
|
// "$ref": "#/definitions/accountRelationship"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountUnblockPOSTHandler(c *gin.Context) {
|
func (m *Module) AccountUnblockPOSTHandler(c *gin.Context) {
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
err := errors.New("no account id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
relationship, errWithCode := m.processor.AccountBlockRemove(c.Request.Context(), authed, targetAcctID)
|
relationship, errWithCode := m.processor.AccountBlockRemove(c.Request.Context(), authed, targetAcctID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,12 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,37 +56,38 @@ import (
|
||||||
// description: Your relationship to this account.
|
// description: Your relationship to this account.
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/accountRelationship"
|
// "$ref": "#/definitions/accountRelationship"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountUnfollowPOSTHandler(c *gin.Context) {
|
func (m *Module) AccountUnfollowPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "AccountUnfollowPOSTHandler")
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debug(err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
l.Debug(err)
|
err := errors.New("no account id specified")
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
relationship, errWithCode := m.processor.AccountFollowRemove(c.Request.Context(), authed, targetAcctID)
|
relationship, errWithCode := m.processor.AccountFollowRemove(c.Request.Context(), authed, targetAcctID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debug(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,14 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -72,53 +74,47 @@ import (
|
||||||
// description: unauthorized
|
// description: unauthorized
|
||||||
// '403':
|
// '403':
|
||||||
// description: forbidden
|
// description: forbidden
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) AccountActionPOSTHandler(c *gin.Context) {
|
func (m *Module) AccountActionPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "AccountActionPOSTHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// make sure we're authed...
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// with an admin account
|
|
||||||
if !authed.User.Admin {
|
if !authed.User.Admin {
|
||||||
l.Debugf("user %s not an admin", authed.User.ID)
|
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
|
api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the form from the request context
|
|
||||||
l.Tracef("parsing request form: %+v", c.Request.Form)
|
|
||||||
form := &model.AdminAccountActionRequest{}
|
form := &model.AdminAccountActionRequest{}
|
||||||
if err := c.ShouldBind(form); err != nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
l.Debugf("error parsing form %+v: %s", c.Request.Form, err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not parse form: %s", err)})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Type == "" {
|
if form.Type == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no type specified"})
|
err := errors.New("no type specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
err := errors.New("no account id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
form.TargetAccountID = targetAcctID
|
form.TargetAccountID = targetAcctID
|
||||||
|
|
||||||
if errWithCode := m.processor.AdminAccountAction(c.Request.Context(), authed, form); errWithCode != nil {
|
if errWithCode := m.processor.AdminAccountAction(c.Request.Context(), authed, form); errWithCode != nil {
|
||||||
l.Debugf("error performing account action: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -86,33 +86,33 @@ import (
|
||||||
// Note that if a list has been imported, then an `array` of newly created domain blocks will be returned instead.
|
// Note that if a list has been imported, then an `array` of newly created domain blocks will be returned instead.
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/domainBlock"
|
// "$ref": "#/definitions/domainBlock"
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {
|
func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "DomainBlocksPOSTHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// make sure we're authed with an admin account
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authed.User.Admin {
|
if !authed.User.Admin {
|
||||||
l.Debugf("user %s not an admin", authed.User.ID)
|
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
|
api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,49 +121,43 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {
|
||||||
if importString != "" {
|
if importString != "" {
|
||||||
i, err := strconv.ParseBool(importString)
|
i, err := strconv.ParseBool(importString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing import string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", ImportQueryKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse import query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
imp = i
|
imp = i
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the media create form from the request context
|
|
||||||
l.Tracef("parsing request form: %+v", c.Request.Form)
|
|
||||||
form := &model.DomainBlockCreateRequest{}
|
form := &model.DomainBlockCreateRequest{}
|
||||||
if err := c.ShouldBind(form); err != nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
l.Debugf("error parsing form %+v: %s", c.Request.Form, err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not parse form: %s", err)})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give the fields on the request form a first pass to make sure the request is superficially valid.
|
|
||||||
l.Tracef("validating form %+v", form)
|
|
||||||
if err := validateCreateDomainBlock(form, imp); err != nil {
|
if err := validateCreateDomainBlock(form, imp); err != nil {
|
||||||
l.Debugf("error validating form: %s", err)
|
err := fmt.Errorf("error validating form: %s", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if imp {
|
if imp {
|
||||||
// we're importing multiple blocks
|
// we're importing multiple blocks
|
||||||
domainBlocks, err := m.processor.AdminDomainBlocksImport(c.Request.Context(), authed, form)
|
domainBlocks, errWithCode := m.processor.AdminDomainBlocksImport(c.Request.Context(), authed, form)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error importing domain blocks: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, domainBlocks)
|
c.JSON(http.StatusOK, domainBlocks)
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// we're just creating one block
|
// we're just creating one block
|
||||||
domainBlock, err := m.processor.AdminDomainBlockCreate(c.Request.Context(), authed, form)
|
domainBlock, errWithCode := m.processor.AdminDomainBlockCreate(c.Request.Context(), authed, form)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error creating domain block: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, domainBlock)
|
c.JSON(http.StatusOK, domainBlock)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCreateDomainBlock(form *model.DomainBlockCreateRequest, imp bool) error {
|
func validateCreateDomainBlock(form *model.DomainBlockCreateRequest, imp bool) error {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,48 +38,46 @@ import (
|
||||||
// description: The domain block that was just deleted.
|
// description: The domain block that was just deleted.
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/domainBlock"
|
// "$ref": "#/definitions/domainBlock"
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) DomainBlockDELETEHandler(c *gin.Context) {
|
func (m *Module) DomainBlockDELETEHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "DomainBlockDELETEHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// make sure we're authed with an admin account
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authed.User.Admin {
|
if !authed.User.Admin {
|
||||||
l.Debugf("user %s not an admin", authed.User.ID)
|
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
|
api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domainBlockID := c.Param(IDKey)
|
domainBlockID := c.Param(IDKey)
|
||||||
if domainBlockID == "" {
|
if domainBlockID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no domain block id provided"})
|
err := errors.New("no domain block id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domainBlock, errWithCode := m.processor.AdminDomainBlockDelete(c.Request.Context(), authed, domainBlockID)
|
domainBlock, errWithCode := m.processor.AdminDomainBlockDelete(c.Request.Context(), authed, domainBlockID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error deleting domain block: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,41 +39,40 @@ import (
|
||||||
// description: The requested domain block.
|
// description: The requested domain block.
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/domainBlock"
|
// "$ref": "#/definitions/domainBlock"
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) DomainBlockGETHandler(c *gin.Context) {
|
func (m *Module) DomainBlockGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "DomainBlockGETHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// make sure we're authed with an admin account
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authed.User.Admin {
|
if !authed.User.Admin {
|
||||||
l.Debugf("user %s not an admin", authed.User.ID)
|
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
|
api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domainBlockID := c.Param(IDKey)
|
domainBlockID := c.Param(IDKey)
|
||||||
if domainBlockID == "" {
|
if domainBlockID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no domain block id provided"})
|
err := errors.New("no domain block id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,17 +81,16 @@ func (m *Module) DomainBlockGETHandler(c *gin.Context) {
|
||||||
if exportString != "" {
|
if exportString != "" {
|
||||||
i, err := strconv.ParseBool(exportString)
|
i, err := strconv.ParseBool(exportString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing export string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", ExportQueryKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse export query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
export = i
|
export = i
|
||||||
}
|
}
|
||||||
|
|
||||||
domainBlock, err := m.processor.AdminDomainBlockGet(c.Request.Context(), authed, domainBlockID, export)
|
domainBlock, errWithCode := m.processor.AdminDomainBlockGet(c.Request.Context(), authed, domainBlockID, export)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error getting domain block: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,35 +45,40 @@ import (
|
||||||
// type: array
|
// type: array
|
||||||
// items:
|
// items:
|
||||||
// "$ref": "#/definitions/domainBlock"
|
// "$ref": "#/definitions/domainBlock"
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) DomainBlocksGETHandler(c *gin.Context) {
|
func (m *Module) DomainBlocksGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "DomainBlocksGETHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// make sure we're authed with an admin account
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authed.User.Admin {
|
if !authed.User.Admin {
|
||||||
l.Debugf("user %s not an admin", authed.User.ID)
|
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
|
api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domainBlockID := c.Param(IDKey)
|
||||||
|
if domainBlockID == "" {
|
||||||
|
err := errors.New("no domain block id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,17 +87,16 @@ func (m *Module) DomainBlocksGETHandler(c *gin.Context) {
|
||||||
if exportString != "" {
|
if exportString != "" {
|
||||||
i, err := strconv.ParseBool(exportString)
|
i, err := strconv.ParseBool(exportString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing export string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", ExportQueryKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse export query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
export = i
|
export = i
|
||||||
}
|
}
|
||||||
|
|
||||||
domainBlocks, err := m.processor.AdminDomainBlocksGet(c.Request.Context(), authed, export)
|
domainBlocks, errWithCode := m.processor.AdminDomainBlocksGet(c.Request.Context(), authed, export)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error getting domain blocks: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||||
)
|
)
|
||||||
|
@ -69,59 +69,52 @@ import (
|
||||||
// description: The newly-created emoji.
|
// description: The newly-created emoji.
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/emoji"
|
// "$ref": "#/definitions/emoji"
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
// '409':
|
// '409':
|
||||||
// description: conflict -- domain/shortcode combo for emoji already exists
|
// description: conflict -- domain/shortcode combo for emoji already exists
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) EmojiCreatePOSTHandler(c *gin.Context) {
|
func (m *Module) EmojiCreatePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
"func": "emojiCreatePOSTHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// make sure we're authed with an admin account
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true) // posting a status is serious business so we want *everything*
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authed.User.Admin {
|
if !authed.User.Admin {
|
||||||
l.Debugf("user %s not an admin", authed.User.ID)
|
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
|
api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the media create form from the request context
|
|
||||||
l.Tracef("parsing request form: %+v", c.Request.Form)
|
|
||||||
form := &model.EmojiCreateRequest{}
|
form := &model.EmojiCreateRequest{}
|
||||||
if err := c.ShouldBind(form); err != nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
l.Debugf("error parsing form %+v: %s", c.Request.Form, err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not parse form: %s", err)})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give the fields on the request form a first pass to make sure the request is superficially valid.
|
|
||||||
l.Tracef("validating form %+v", form)
|
|
||||||
if err := validateCreateEmoji(form); err != nil {
|
if err := validateCreateEmoji(form); err != nil {
|
||||||
l.Debugf("error validating form: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiEmoji, errWithCode := m.processor.AdminEmojiCreate(c.Request.Context(), authed, form)
|
apiEmoji, errWithCode := m.processor.AdminEmojiCreate(c.Request.Context(), authed, form)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error creating emoji: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +122,6 @@ func (m *Module) EmojiCreatePOSTHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCreateEmoji(form *model.EmojiCreateRequest) error {
|
func validateCreateEmoji(form *model.EmojiCreateRequest) error {
|
||||||
// check there actually is an image attached and it's not size 0
|
|
||||||
if form.Image == nil || form.Image.Size == 0 {
|
if form.Image == nil || form.Image.Size == 0 {
|
||||||
return errors.New("no emoji given")
|
return errors.New("no emoji given")
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ func (suite *EmojiCreateTestSuite) TestEmojiCreateAlreadyExists() {
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.NotEmpty(b)
|
suite.NotEmpty(b)
|
||||||
|
|
||||||
suite.Equal(`{"error":"conflict: emoji with shortcode rainbow already exists"}`, string(b))
|
suite.Equal(`{"error":"Conflict: emoji with shortcode rainbow already exists"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmojiCreateTestSuite(t *testing.T) {
|
func TestEmojiCreateTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -23,9 +23,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,39 +55,34 @@ import (
|
||||||
// '200':
|
// '200':
|
||||||
// description: |-
|
// description: |-
|
||||||
// Echos the number of days requested. The cleanup is performed asynchronously after the request completes.
|
// Echos the number of days requested. The cleanup is performed asynchronously after the request completes.
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) {
|
func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "MediaCleanupPOSTHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// make sure we're authed...
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// with an admin account
|
|
||||||
if !authed.User.Admin {
|
if !authed.User.Admin {
|
||||||
l.Debugf("user %s not an admin", authed.User.ID)
|
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
|
api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the form from the request context
|
|
||||||
l.Tracef("parsing request form: %+v", c.Request.Form)
|
|
||||||
form := &model.MediaCleanupRequest{}
|
form := &model.MediaCleanupRequest{}
|
||||||
if err := c.ShouldBind(form); err != nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
l.Debugf("error parsing form %+v: %s", c.Request.Form, err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not parse form: %s", err)})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,8 +97,7 @@ func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if errWithCode := m.processor.AdminMediaPrune(c.Request.Context(), remoteCacheDays); errWithCode != nil {
|
if errWithCode := m.processor.AdminMediaPrune(c.Request.Context(), remoteCacheDays); errWithCode != nil {
|
||||||
l.Debugf("error starting prune of remote media: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,18 +22,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// these consts are used to ensure users can't spam huge entries into our database
|
||||||
const (
|
const (
|
||||||
// permitted length for most fields
|
|
||||||
formFieldLen = 64
|
formFieldLen = 64
|
||||||
// redirect can be a bit bigger because we probably need to encode data in the redirect uri
|
|
||||||
formRedirectLen = 512
|
formRedirectLen = 512
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,56 +62,63 @@ const (
|
||||||
// description: "The newly-created application."
|
// description: "The newly-created application."
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/application"
|
// "$ref": "#/definitions/application"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
// '422':
|
// '401':
|
||||||
// description: unprocessable
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
// '500':
|
// '500':
|
||||||
// description: internal error
|
// description: internal server error
|
||||||
func (m *Module) AppsPOSTHandler(c *gin.Context) {
|
func (m *Module) AppsPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "AppsPOSTHandler")
|
|
||||||
l.Trace("entering AppsPOSTHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, false, false, false, false)
|
authed, err := oauth.Authed(c, false, false, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form := &model.ApplicationCreateRequest{}
|
form := &model.ApplicationCreateRequest{}
|
||||||
if err := c.ShouldBind(form); err != nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check lengths of fields before proceeding so the user can't spam huge entries into the database
|
|
||||||
if len(form.ClientName) > formFieldLen {
|
if len(form.ClientName) > formFieldLen {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("client_name must be less than %d bytes", formFieldLen)})
|
err := fmt.Errorf("client_name must be less than %d bytes", formFieldLen)
|
||||||
return
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
}
|
|
||||||
if len(form.Website) > formFieldLen {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("website must be less than %d bytes", formFieldLen)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(form.RedirectURIs) > formRedirectLen {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("redirect_uris must be less than %d bytes", formRedirectLen)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(form.Scopes) > formFieldLen {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("scopes must be less than %d bytes", formFieldLen)})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiApp, err := m.processor.AppCreate(c.Request.Context(), authed, form)
|
if len(form.RedirectURIs) > formRedirectLen {
|
||||||
if err != nil {
|
err := fmt.Errorf("redirect_uris must be less than %d bytes", formRedirectLen)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(form.Scopes) > formFieldLen {
|
||||||
|
err := fmt.Errorf("scopes must be less than %d bytes", formFieldLen)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(form.Website) > formFieldLen {
|
||||||
|
err := fmt.Errorf("website must be less than %d bytes", formFieldLen)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiApp, errWithCode := m.processor.AppCreate(c.Request.Context(), authed, form)
|
||||||
|
if errWithCode != nil {
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oidc"
|
"github.com/superseriousbusiness/gotosocial/internal/oidc"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/router"
|
"github.com/superseriousbusiness/gotosocial/internal/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -69,14 +70,16 @@ type Module struct {
|
||||||
db db.DB
|
db db.DB
|
||||||
server oauth.Server
|
server oauth.Server
|
||||||
idp oidc.IDP
|
idp oidc.IDP
|
||||||
|
processor processing.Processor
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new auth module
|
// New returns a new auth module
|
||||||
func New(db db.DB, server oauth.Server, idp oidc.IDP) api.ClientModule {
|
func New(db db.DB, server oauth.Server, idp oidc.IDP, processor processing.Processor) api.ClientModule {
|
||||||
return &Module{
|
return &Module{
|
||||||
db: db,
|
db: db,
|
||||||
server: server,
|
server: server,
|
||||||
idp: idp,
|
idp: idp,
|
||||||
|
processor: processor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,16 +23,23 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
||||||
|
"codeberg.org/gruf/go-store/kv"
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-contrib/sessions/memstore"
|
"github.com/gin-contrib/sessions/memstore"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/auth"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/auth"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/concurrency"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oidc"
|
"github.com/superseriousbusiness/gotosocial/internal/oidc"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/router"
|
"github.com/superseriousbusiness/gotosocial/internal/router"
|
||||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||||
)
|
)
|
||||||
|
@ -40,6 +47,11 @@ import (
|
||||||
type AuthStandardTestSuite struct {
|
type AuthStandardTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
db db.DB
|
db db.DB
|
||||||
|
storage *kv.KVStore
|
||||||
|
mediaManager media.Manager
|
||||||
|
federator federation.Federator
|
||||||
|
processor processing.Processor
|
||||||
|
emailSender email.Sender
|
||||||
idp oidc.IDP
|
idp oidc.IDP
|
||||||
oauthServer oauth.Server
|
oauthServer oauth.Server
|
||||||
|
|
||||||
|
@ -69,17 +81,26 @@ func (suite *AuthStandardTestSuite) SetupSuite() {
|
||||||
|
|
||||||
func (suite *AuthStandardTestSuite) SetupTest() {
|
func (suite *AuthStandardTestSuite) SetupTest() {
|
||||||
testrig.InitTestConfig()
|
testrig.InitTestConfig()
|
||||||
suite.db = testrig.NewTestDB()
|
|
||||||
testrig.InitTestLog()
|
testrig.InitTestLog()
|
||||||
|
|
||||||
|
fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
|
||||||
|
clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1)
|
||||||
|
|
||||||
|
suite.db = testrig.NewTestDB()
|
||||||
|
suite.storage = testrig.NewTestStorage()
|
||||||
|
suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage)
|
||||||
|
suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)
|
||||||
|
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||||
|
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker)
|
||||||
|
|
||||||
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
|
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
|
||||||
var err error
|
var err error
|
||||||
suite.idp, err = oidc.NewIDP(context.Background())
|
suite.idp, err = oidc.NewIDP(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
suite.authModule = auth.New(suite.db, suite.oauthServer, suite.idp).(*auth.Module)
|
suite.authModule = auth.New(suite.db, suite.oauthServer, suite.idp, suite.processor).(*auth.Module)
|
||||||
testrig.StandardDBSetup(suite.db, nil)
|
testrig.StandardDBSetup(suite.db, suite.testAccounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AuthStandardTestSuite) TearDownTest() {
|
func (suite *AuthStandardTestSuite) TearDownTest() {
|
||||||
|
@ -92,7 +113,7 @@ func (suite *AuthStandardTestSuite) newContext(requestMethod string, requestPath
|
||||||
ctx, engine := gin.CreateTestContext(recorder)
|
ctx, engine := gin.CreateTestContext(recorder)
|
||||||
|
|
||||||
// load templates into the engine
|
// load templates into the engine
|
||||||
testrig.ConfigureTemplatesWithGin(engine)
|
testrig.ConfigureTemplatesWithGin(engine, "../../../../web/template")
|
||||||
|
|
||||||
// create the request
|
// create the request
|
||||||
protocol := config.GetProtocol()
|
protocol := config.GetProtocol()
|
||||||
|
|
|
@ -23,9 +23,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -33,18 +30,22 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// helpfulAdvice is a handy hint to users;
|
||||||
|
// particularly important during the login flow
|
||||||
|
var helpfulAdvice = "If you arrived at this error during a login/oauth flow, please try clearing your session cookies and logging in again; if problems persist, make sure you're using the correct credentials"
|
||||||
|
|
||||||
// AuthorizeGETHandler should be served as GET at https://example.org/oauth/authorize
|
// AuthorizeGETHandler should be served as GET at https://example.org/oauth/authorize
|
||||||
// The idea here is to present an oauth authorize page to the user, with a button
|
// The idea here is to present an oauth authorize page to the user, with a button
|
||||||
// that they have to click to accept.
|
// that they have to click to accept.
|
||||||
func (m *Module) AuthorizeGETHandler(c *gin.Context) {
|
func (m *Module) AuthorizeGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "AuthorizeGETHandler")
|
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.HTMLAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.HTMLAcceptHeaders...); err != nil {
|
||||||
c.HTML(http.StatusNotAcceptable, "error.tmpl", gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,56 +53,75 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
|
||||||
// If it's not set, then we don't know yet who the user is, so we need to redirect them to the sign in page.
|
// If it's not set, then we don't know yet who the user is, so we need to redirect them to the sign in page.
|
||||||
userID, ok := s.Get(sessionUserID).(string)
|
userID, ok := s.Get(sessionUserID).(string)
|
||||||
if !ok || userID == "" {
|
if !ok || userID == "" {
|
||||||
l.Trace("userid was empty, parsing form then redirecting to sign in page")
|
|
||||||
form := &model.OAuthAuthorize{}
|
form := &model.OAuthAuthorize{}
|
||||||
if err := c.Bind(form); err != nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
l.Debugf("invalid auth form: %s", err)
|
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Debugf("parsed auth form: %+v", form)
|
|
||||||
|
|
||||||
if err := extractAuthForm(s, form); err != nil {
|
if errWithCode := saveAuthFormToSession(s, form); errWithCode != nil {
|
||||||
l.Debugf(fmt.Sprintf("error parsing form at /oauth/authorize: %s", err))
|
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"error": err.Error()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusSeeOther, AuthSignInPath)
|
c.Redirect(http.StatusSeeOther, AuthSignInPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can use the client_id on the session to retrieve info about the app associated with the client_id
|
// use session information to validate app, user, and account for this request
|
||||||
clientID, ok := s.Get(sessionClientID).(string)
|
clientID, ok := s.Get(sessionClientID).(string)
|
||||||
if !ok || clientID == "" {
|
if !ok || clientID == "" {
|
||||||
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"error": "no client_id found in session"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
app := >smodel.Application{}
|
|
||||||
if err := m.db.GetWhere(c.Request.Context(), []db.Where{{Key: sessionClientID, Value: clientID}}, app); err != nil {
|
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
err := fmt.Errorf("key %s was not found in session", sessionClientID)
|
||||||
"error": fmt.Sprintf("no application found for client id %s", clientID),
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
|
||||||
})
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app := >smodel.Application{}
|
||||||
|
if err := m.db.GetWhere(c.Request.Context(), []db.Where{{Key: sessionClientID, Value: clientID}}, app); err != nil {
|
||||||
|
m.clearSession(s)
|
||||||
|
safe := fmt.Sprintf("application for %s %s could not be retrieved", sessionClientID, clientID)
|
||||||
|
var errWithCode gtserror.WithCode
|
||||||
|
if err == db.ErrNoEntries {
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
|
||||||
|
} else {
|
||||||
|
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
|
||||||
|
}
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect the user if they have not confirmed their email yet, thier account has not been approved yet,
|
|
||||||
// or thier account has been disabled.
|
|
||||||
user := >smodel.User{}
|
user := >smodel.User{}
|
||||||
if err := m.db.GetByID(c.Request.Context(), userID, user); err != nil {
|
if err := m.db.GetByID(c.Request.Context(), userID, user); err != nil {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"error": err.Error()})
|
safe := fmt.Sprintf("user with id %s could not be retrieved", userID)
|
||||||
|
var errWithCode gtserror.WithCode
|
||||||
|
if err == db.ErrNoEntries {
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
|
||||||
|
} else {
|
||||||
|
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
|
||||||
|
}
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID)
|
acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"error": err.Error()})
|
safe := fmt.Sprintf("account with id %s could not be retrieved", user.AccountID)
|
||||||
|
var errWithCode gtserror.WithCode
|
||||||
|
if err == db.ErrNoEntries {
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
|
||||||
|
} else {
|
||||||
|
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
|
||||||
|
}
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ensureUserIsAuthorizedOrRedirect(c, user, acct) {
|
|
||||||
|
if ensureUserIsAuthorizedOrRedirect(c, user, acct) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,25 +129,27 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
|
||||||
redirect, ok := s.Get(sessionRedirectURI).(string)
|
redirect, ok := s.Get(sessionRedirectURI).(string)
|
||||||
if !ok || redirect == "" {
|
if !ok || redirect == "" {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"error": "no redirect_uri found in session"})
|
err := fmt.Errorf("key %s was not found in session", sessionRedirectURI)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scope, ok := s.Get(sessionScope).(string)
|
scope, ok := s.Get(sessionScope).(string)
|
||||||
if !ok || scope == "" {
|
if !ok || scope == "" {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"error": "no scope found in session"})
|
err := fmt.Errorf("key %s was not found in session", sessionScope)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// the authorize template will display a form to the user where they can get some information
|
// the authorize template will display a form to the user where they can get some information
|
||||||
// about the app that's trying to authorize, and the scope of the request.
|
// about the app that's trying to authorize, and the scope of the request.
|
||||||
// They can then approve it if it looks OK to them, which will POST to the AuthorizePOSTHandler
|
// They can then approve it if it looks OK to them, which will POST to the AuthorizePOSTHandler
|
||||||
l.Trace("serving authorize html")
|
|
||||||
c.HTML(http.StatusOK, "authorize.tmpl", gin.H{
|
c.HTML(http.StatusOK, "authorize.tmpl", gin.H{
|
||||||
"appname": app.Name,
|
"appname": app.Name,
|
||||||
"appwebsite": app.Website,
|
"appwebsite": app.Website,
|
||||||
"redirect": redirect,
|
"redirect": redirect,
|
||||||
sessionScope: scope,
|
"scope": scope,
|
||||||
"user": acct.Username,
|
"user": acct.Username,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -136,13 +158,10 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
|
||||||
// At this point we assume that the user has A) logged in and B) accepted that the app should act for them,
|
// At this point we assume that the user has A) logged in and B) accepted that the app should act for them,
|
||||||
// so we should proceed with the authentication flow and generate an oauth token for them if we can.
|
// so we should proceed with the authentication flow and generate an oauth token for them if we can.
|
||||||
func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
|
func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "AuthorizePOSTHandler")
|
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
|
|
||||||
// We need to retrieve the original form submitted to the authorizeGEThandler, and
|
// We need to retrieve the original form submitted to the authorizeGEThandler, and
|
||||||
// recreate it on the request so that it can be used further by the oauth2 library.
|
// recreate it on the request so that it can be used further by the oauth2 library.
|
||||||
// So first fetch all the values from the session.
|
|
||||||
|
|
||||||
errs := []string{}
|
errs := []string{}
|
||||||
|
|
||||||
forceLogin, ok := s.Get(sessionForceLogin).(string)
|
forceLogin, ok := s.Get(sessionForceLogin).(string)
|
||||||
|
@ -152,77 +171,107 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
|
||||||
|
|
||||||
responseType, ok := s.Get(sessionResponseType).(string)
|
responseType, ok := s.Get(sessionResponseType).(string)
|
||||||
if !ok || responseType == "" {
|
if !ok || responseType == "" {
|
||||||
errs = append(errs, "session missing response_type")
|
errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionResponseType))
|
||||||
}
|
}
|
||||||
|
|
||||||
clientID, ok := s.Get(sessionClientID).(string)
|
clientID, ok := s.Get(sessionClientID).(string)
|
||||||
if !ok || clientID == "" {
|
if !ok || clientID == "" {
|
||||||
errs = append(errs, "session missing client_id")
|
errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionClientID))
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectURI, ok := s.Get(sessionRedirectURI).(string)
|
redirectURI, ok := s.Get(sessionRedirectURI).(string)
|
||||||
if !ok || redirectURI == "" {
|
if !ok || redirectURI == "" {
|
||||||
errs = append(errs, "session missing redirect_uri")
|
errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionRedirectURI))
|
||||||
}
|
}
|
||||||
|
|
||||||
scope, ok := s.Get(sessionScope).(string)
|
scope, ok := s.Get(sessionScope).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
errs = append(errs, "session missing scope")
|
errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionScope))
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, ok := s.Get(sessionUserID).(string)
|
userID, ok := s.Get(sessionUserID).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
errs = append(errs, "session missing userid")
|
errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionUserID))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) != 0 {
|
||||||
|
errs = append(errs, helpfulAdvice)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(errors.New("one or more missing keys on session during AuthorizePOSTHandler"), errs...), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect the user if they have not confirmed their email yet, thier account has not been approved yet,
|
|
||||||
// or thier account has been disabled.
|
|
||||||
user := >smodel.User{}
|
user := >smodel.User{}
|
||||||
if err := m.db.GetByID(c.Request.Context(), userID, user); err != nil {
|
if err := m.db.GetByID(c.Request.Context(), userID, user); err != nil {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"error": err.Error()})
|
safe := fmt.Sprintf("user with id %s could not be retrieved", userID)
|
||||||
|
var errWithCode gtserror.WithCode
|
||||||
|
if err == db.ErrNoEntries {
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
|
||||||
|
} else {
|
||||||
|
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
|
||||||
|
}
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID)
|
acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{"error": err.Error()})
|
safe := fmt.Sprintf("account with id %s could not be retrieved", user.AccountID)
|
||||||
return
|
var errWithCode gtserror.WithCode
|
||||||
|
if err == db.ErrNoEntries {
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
|
||||||
|
} else {
|
||||||
|
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
|
||||||
}
|
}
|
||||||
if !ensureUserIsAuthorizedOrRedirect(c, user, acct) {
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ensureUserIsAuthorizedOrRedirect(c, user, acct) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're done with the session now, so just clear it out
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
|
|
||||||
if len(errs) != 0 {
|
// we have to set the values on the request form
|
||||||
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"error": strings.Join(errs, ": ")})
|
// so that they're picked up by the oauth server
|
||||||
return
|
c.Request.Form = url.Values{
|
||||||
|
sessionForceLogin: {forceLogin},
|
||||||
|
sessionResponseType: {responseType},
|
||||||
|
sessionClientID: {clientID},
|
||||||
|
sessionRedirectURI: {redirectURI},
|
||||||
|
sessionScope: {scope},
|
||||||
|
sessionUserID: {userID},
|
||||||
}
|
}
|
||||||
|
|
||||||
// now set the values on the request
|
|
||||||
values := url.Values{}
|
|
||||||
values.Set(sessionForceLogin, forceLogin)
|
|
||||||
values.Set(sessionResponseType, responseType)
|
|
||||||
values.Set(sessionClientID, clientID)
|
|
||||||
values.Set(sessionRedirectURI, redirectURI)
|
|
||||||
values.Set(sessionScope, scope)
|
|
||||||
values.Set(sessionUserID, userID)
|
|
||||||
c.Request.Form = values
|
|
||||||
l.Tracef("values on request set to %+v", c.Request.Form)
|
|
||||||
|
|
||||||
// and proceed with authorization using the oauth2 library
|
|
||||||
if err := m.server.HandleAuthorizeRequest(c.Writer, c.Request); err != nil {
|
if err := m.server.HandleAuthorizeRequest(c.Writer, c.Request); err != nil {
|
||||||
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice), m.processor.InstanceGet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractAuthForm checks the given OAuthAuthorize form, and stores
|
// saveAuthFormToSession checks the given OAuthAuthorize form,
|
||||||
// the values in the form into the session.
|
// and stores the values in the form into the session.
|
||||||
func extractAuthForm(s sessions.Session, form *model.OAuthAuthorize) error {
|
func saveAuthFormToSession(s sessions.Session, form *model.OAuthAuthorize) gtserror.WithCode {
|
||||||
// these fields are *required* so check 'em
|
if form == nil {
|
||||||
if form.ResponseType == "" || form.ClientID == "" || form.RedirectURI == "" {
|
err := errors.New("OAuthAuthorize form was nil")
|
||||||
return errors.New("missing one of: response_type, client_id or redirect_uri")
|
return gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.ResponseType == "" {
|
||||||
|
err := errors.New("field response_type was not set on OAuthAuthorize form")
|
||||||
|
return gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.ClientID == "" {
|
||||||
|
err := errors.New("field client_id was not set on OAuthAuthorize form")
|
||||||
|
return gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.RedirectURI == "" {
|
||||||
|
err := errors.New("field redirect_uri was not set on OAuthAuthorize form")
|
||||||
|
return gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set default scope to read
|
// set default scope to read
|
||||||
|
@ -237,29 +286,33 @@ func extractAuthForm(s sessions.Session, form *model.OAuthAuthorize) error {
|
||||||
s.Set(sessionRedirectURI, form.RedirectURI)
|
s.Set(sessionRedirectURI, form.RedirectURI)
|
||||||
s.Set(sessionScope, form.Scope)
|
s.Set(sessionScope, form.Scope)
|
||||||
s.Set(sessionState, uuid.NewString())
|
s.Set(sessionState, uuid.NewString())
|
||||||
return s.Save()
|
|
||||||
|
if err := s.Save(); err != nil {
|
||||||
|
err := fmt.Errorf("error saving form values onto session: %s", err)
|
||||||
|
return gtserror.NewErrorInternalError(err, helpfulAdvice)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureUserIsAuthorizedOrRedirect(ctx *gin.Context, user *gtsmodel.User, account *gtsmodel.Account) bool {
|
func ensureUserIsAuthorizedOrRedirect(ctx *gin.Context, user *gtsmodel.User, account *gtsmodel.Account) (redirected bool) {
|
||||||
if user.ConfirmedAt.IsZero() {
|
if user.ConfirmedAt.IsZero() {
|
||||||
ctx.Redirect(http.StatusSeeOther, CheckYourEmailPath)
|
ctx.Redirect(http.StatusSeeOther, CheckYourEmailPath)
|
||||||
return false
|
redirected = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !user.Approved {
|
if !user.Approved {
|
||||||
ctx.Redirect(http.StatusSeeOther, WaitForApprovalPath)
|
ctx.Redirect(http.StatusSeeOther, WaitForApprovalPath)
|
||||||
return false
|
redirected = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Disabled {
|
if user.Disabled || !account.SuspendedAt.IsZero() {
|
||||||
ctx.Redirect(http.StatusSeeOther, AccountDisabledPath)
|
ctx.Redirect(http.StatusSeeOther, AccountDisabledPath)
|
||||||
return false
|
redirected = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !account.SuspendedAt.IsZero() {
|
return
|
||||||
ctx.Redirect(http.StatusSeeOther, AccountDisabledPath)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,9 @@ import (
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oidc"
|
"github.com/superseriousbusiness/gotosocial/internal/oidc"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||||
|
@ -40,11 +42,14 @@ import (
|
||||||
func (m *Module) CallbackGETHandler(c *gin.Context) {
|
func (m *Module) CallbackGETHandler(c *gin.Context) {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
|
|
||||||
// first make sure the state set in the cookie is the same as the state returned from the external provider
|
// check the query vs session state parameter to mitigate csrf
|
||||||
|
// https://auth0.com/docs/secure/attack-protection/state-parameters
|
||||||
|
|
||||||
state := c.Query(callbackStateParam)
|
state := c.Query(callbackStateParam)
|
||||||
if state == "" {
|
if state == "" {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "state query not found on callback"})
|
err := fmt.Errorf("%s parameter not found on callback query", callbackStateParam)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,84 +57,104 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {
|
||||||
savedState, ok := savedStateI.(string)
|
savedState, ok := savedStateI.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "state not found in session"})
|
err := fmt.Errorf("key %s was not found in session", sessionState)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if state != savedState {
|
if state != savedState {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "state mismatch"})
|
err := errors.New("mismatch between query state and session state")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// retrieve stored claims using code
|
||||||
code := c.Query(callbackCodeParam)
|
code := c.Query(callbackCodeParam)
|
||||||
|
if code == "" {
|
||||||
claims, err := m.idp.HandleCallback(c.Request.Context(), code)
|
|
||||||
if err != nil {
|
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
err := fmt.Errorf("%s parameter not found on callback query", callbackCodeParam)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can use the client_id on the session to retrieve info about the app associated with the client_id
|
claims, errWithCode := m.idp.HandleCallback(c.Request.Context(), code)
|
||||||
|
if errWithCode != nil {
|
||||||
|
m.clearSession(s)
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can use the client_id on the session to retrieve
|
||||||
|
// info about the app associated with the client_id
|
||||||
clientID, ok := s.Get(sessionClientID).(string)
|
clientID, ok := s.Get(sessionClientID).(string)
|
||||||
if !ok || clientID == "" {
|
if !ok || clientID == "" {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "no client_id found in session during callback"})
|
err := fmt.Errorf("key %s was not found in session", sessionClientID)
|
||||||
return
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
|
||||||
}
|
|
||||||
app := >smodel.Application{}
|
|
||||||
if err := m.db.GetWhere(c.Request.Context(), []db.Where{{Key: sessionClientID, Value: clientID}}, app); err != nil {
|
|
||||||
m.clearSession(s)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("no application found for client id %s", clientID)})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := m.parseUserFromClaims(c.Request.Context(), claims, net.IP(c.ClientIP()), app.ID)
|
app := >smodel.Application{}
|
||||||
if err != nil {
|
if err := m.db.GetWhere(c.Request.Context(), []db.Where{{Key: sessionClientID, Value: clientID}}, app); err != nil {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
safe := fmt.Sprintf("application for %s %s could not be retrieved", sessionClientID, clientID)
|
||||||
|
var errWithCode gtserror.WithCode
|
||||||
|
if err == db.ErrNoEntries {
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
|
||||||
|
} else {
|
||||||
|
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
|
||||||
|
}
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, errWithCode := m.parseUserFromClaims(c.Request.Context(), claims, net.IP(c.ClientIP()), app.ID)
|
||||||
|
if errWithCode != nil {
|
||||||
|
m.clearSession(s)
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Set(sessionUserID, user.ID)
|
s.Set(sessionUserID, user.ID)
|
||||||
if err := s.Save(); err != nil {
|
if err := s.Save(); err != nil {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, OauthAuthorizePath)
|
c.Redirect(http.StatusFound, OauthAuthorizePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, ip net.IP, appID string) (*gtsmodel.User, error) {
|
func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, ip net.IP, appID string) (*gtsmodel.User, gtserror.WithCode) {
|
||||||
if claims.Email == "" {
|
if claims.Email == "" {
|
||||||
return nil, errors.New("no email returned in claims")
|
err := errors.New("no email returned in claims")
|
||||||
|
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if we already have a user for this email address
|
// see if we already have a user for this email address
|
||||||
|
// if so, we don't need to continue + create one
|
||||||
user := >smodel.User{}
|
user := >smodel.User{}
|
||||||
err := m.db.GetWhere(ctx, []db.Where{{Key: "email", Value: claims.Email}}, user)
|
err := m.db.GetWhere(ctx, []db.Where{{Key: "email", Value: claims.Email}}, user)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// we do! so we can just return it
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != db.ErrNoEntries {
|
if err != db.ErrNoEntries {
|
||||||
// we have an actual error in the database
|
err := fmt.Errorf("error checking database for email %s: %s", claims.Email, err)
|
||||||
return nil, fmt.Errorf("error checking database for email %s: %s", claims.Email, err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// maybe we have an unconfirmed user
|
// maybe we have an unconfirmed user
|
||||||
err = m.db.GetWhere(ctx, []db.Where{{Key: "unconfirmed_email", Value: claims.Email}}, user)
|
err = m.db.GetWhere(ctx, []db.Where{{Key: "unconfirmed_email", Value: claims.Email}}, user)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// user is unconfirmed so return an error
|
err := fmt.Errorf("user with email address %s is unconfirmed", claims.Email)
|
||||||
return nil, fmt.Errorf("user with email address %s is unconfirmed", claims.Email)
|
return nil, gtserror.NewErrorForbidden(err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != db.ErrNoEntries {
|
if err != db.ErrNoEntries {
|
||||||
// we have an actual error in the database
|
err := fmt.Errorf("error checking database for email %s: %s", claims.Email, err)
|
||||||
return nil, fmt.Errorf("error checking database for email %s: %s", claims.Email, err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't have a confirmed or unconfirmed user with the claimed email address
|
// we don't have a confirmed or unconfirmed user with the claimed email address
|
||||||
|
@ -138,10 +163,10 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i
|
||||||
// check if the email address is available for use; if it's not there's nothing we can so
|
// check if the email address is available for use; if it's not there's nothing we can so
|
||||||
emailAvailable, err := m.db.IsEmailAvailable(ctx, claims.Email)
|
emailAvailable, err := m.db.IsEmailAvailable(ctx, claims.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("email %s not available: %s", claims.Email, err)
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
if !emailAvailable {
|
if !emailAvailable {
|
||||||
return nil, fmt.Errorf("email %s in use", claims.Email)
|
return nil, gtserror.NewErrorConflict(fmt.Errorf("email address %s is not available", claims.Email))
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we need a username
|
// now we need a username
|
||||||
|
@ -149,12 +174,12 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i
|
||||||
|
|
||||||
// make sure claims.Name is defined since we'll be using that for the username
|
// make sure claims.Name is defined since we'll be using that for the username
|
||||||
if claims.Name == "" {
|
if claims.Name == "" {
|
||||||
return nil, errors.New("no name returned in claims")
|
err := errors.New("no name returned in claims")
|
||||||
|
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we can just use claims.Name as-is
|
// check if we can just use claims.Name as-is
|
||||||
err = validate.Username(claims.Name)
|
if err = validate.Username(claims.Name); err == nil {
|
||||||
if err == nil {
|
|
||||||
// the name we have on the claims is already a valid username
|
// the name we have on the claims is already a valid username
|
||||||
username = claims.Name
|
username = claims.Name
|
||||||
} else {
|
} else {
|
||||||
|
@ -166,12 +191,12 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i
|
||||||
// lowercase the whole thing
|
// lowercase the whole thing
|
||||||
lower := strings.ToLower(underscored)
|
lower := strings.ToLower(underscored)
|
||||||
// see if this is valid....
|
// see if this is valid....
|
||||||
if err := validate.Username(lower); err == nil {
|
if err := validate.Username(lower); err != nil {
|
||||||
|
err := fmt.Errorf("couldn't parse a valid username from claims.Name value of %s: %s", claims.Name, err)
|
||||||
|
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||||
|
}
|
||||||
// we managed to get a valid username
|
// we managed to get a valid username
|
||||||
username = lower
|
username = lower
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("couldn't parse a valid username from claims.Name value of %s", claims.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var iString string
|
var iString string
|
||||||
|
@ -185,7 +210,7 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i
|
||||||
for i := 1; !found; i++ {
|
for i := 1; !found; i++ {
|
||||||
usernameAvailable, err := m.db.IsUsernameAvailable(ctx, username+iString)
|
usernameAvailable, err := m.db.IsUsernameAvailable(ctx, username+iString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
if usernameAvailable {
|
if usernameAvailable {
|
||||||
// no error so we've found a username that works
|
// no error so we've found a username that works
|
||||||
|
@ -223,7 +248,7 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i
|
||||||
// create the user! this will also create an account and store it in the database so we don't need to do that here
|
// create the user! this will also create an account and store it in the database so we don't need to do that here
|
||||||
user, err = m.db.NewSignup(ctx, username, "", requireApproval, claims.Email, password, ip, "", appID, emailVerified, admin)
|
user, err = m.db.NewSignup(ctx, username, "", requireApproval, claims.Email, password, ip, "", appID, emailVerified, admin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating user: %s", err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
|
|
|
@ -21,14 +21,14 @@ package auth
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
@ -41,64 +41,62 @@ type login struct {
|
||||||
|
|
||||||
// SignInGETHandler should be served at https://example.org/auth/sign_in.
|
// SignInGETHandler should be served at https://example.org/auth/sign_in.
|
||||||
// The idea is to present a sign in page to the user, where they can enter their username and password.
|
// The idea is to present a sign in page to the user, where they can enter their username and password.
|
||||||
// The form will then POST to the sign in page, which will be handled by SignInPOSTHandler
|
// The form will then POST to the sign in page, which will be handled by SignInPOSTHandler.
|
||||||
|
// If an idp provider is set, then the user will be redirected to that to do their sign in.
|
||||||
func (m *Module) SignInGETHandler(c *gin.Context) {
|
func (m *Module) SignInGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "SignInGETHandler")
|
|
||||||
l.Trace("entering sign in handler")
|
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.HTMLAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.HTMLAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.idp != nil {
|
if m.idp == nil {
|
||||||
|
// no idp provider, use our own funky little sign in page
|
||||||
|
c.HTML(http.StatusOK, "sign-in.tmpl", gin.H{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// idp provider is in use, so redirect to it
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
|
|
||||||
stateI := s.Get(sessionState)
|
stateI := s.Get(sessionState)
|
||||||
state, ok := stateI.(string)
|
state, ok := stateI.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "state not found in session"})
|
err := fmt.Errorf("key %s was not found in session", sessionState)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect := m.idp.AuthCodeURL(state)
|
c.Redirect(http.StatusSeeOther, m.idp.AuthCodeURL(state))
|
||||||
l.Debugf("redirecting to external idp at %s", redirect)
|
|
||||||
c.Redirect(http.StatusSeeOther, redirect)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.HTML(http.StatusOK, "sign-in.tmpl", gin.H{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignInPOSTHandler should be served at https://example.org/auth/sign_in.
|
// SignInPOSTHandler should be served at https://example.org/auth/sign_in.
|
||||||
// The idea is to present a sign in page to the user, where they can enter their username and password.
|
// The idea is to present a sign in page to the user, where they can enter their username and password.
|
||||||
// The handler will then redirect to the auth handler served at /auth
|
// The handler will then redirect to the auth handler served at /auth
|
||||||
func (m *Module) SignInPOSTHandler(c *gin.Context) {
|
func (m *Module) SignInPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "SignInPOSTHandler")
|
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
|
|
||||||
form := &login{}
|
form := &login{}
|
||||||
if err := c.ShouldBind(form); err != nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
m.clearSession(s)
|
m.clearSession(s)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Tracef("parsed form: %+v", form)
|
|
||||||
|
|
||||||
userid, err := m.ValidatePassword(c.Request.Context(), form.Email, form.Password)
|
userid, errWithCode := m.ValidatePassword(c.Request.Context(), form.Email, form.Password)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
c.String(http.StatusForbidden, err.Error())
|
// don't clear session here, so the user can just press back and try again
|
||||||
m.clearSession(s)
|
// if they accidentally gave the wrong password or something
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Set(sessionUserID, userid)
|
s.Set(sessionUserID, userid)
|
||||||
if err := s.Save(); err != nil {
|
if err := s.Save(); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
err := fmt.Errorf("error saving user id onto session: %s", err)
|
||||||
m.clearSession(s)
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err, helpfulAdvice), m.processor.InstanceGet)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Trace("redirecting to auth page")
|
|
||||||
c.Redirect(http.StatusFound, OauthAuthorizePath)
|
c.Redirect(http.StatusFound, OauthAuthorizePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,42 +104,34 @@ func (m *Module) SignInPOSTHandler(c *gin.Context) {
|
||||||
// The goal is to authenticate the password against the one for that email
|
// The goal is to authenticate the password against the one for that email
|
||||||
// address stored in the database. If OK, we return the userid (a ulid) for that user,
|
// address stored in the database. If OK, we return the userid (a ulid) for that user,
|
||||||
// so that it can be used in further Oauth flows to generate a token/retreieve an oauth client from the db.
|
// so that it can be used in further Oauth flows to generate a token/retreieve an oauth client from the db.
|
||||||
func (m *Module) ValidatePassword(ctx context.Context, email string, password string) (userid string, err error) {
|
func (m *Module) ValidatePassword(ctx context.Context, email string, password string) (string, gtserror.WithCode) {
|
||||||
l := logrus.WithField("func", "ValidatePassword")
|
|
||||||
|
|
||||||
// make sure an email/password was provided and bail if not
|
|
||||||
if email == "" || password == "" {
|
if email == "" || password == "" {
|
||||||
l.Debug("email or password was not provided")
|
err := errors.New("email or password was not provided")
|
||||||
return incorrectPassword()
|
return incorrectPassword(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// first we select the user from the database based on email address, bail if no user found for that email
|
user := >smodel.User{}
|
||||||
gtsUser := >smodel.User{}
|
if err := m.db.GetWhere(ctx, []db.Where{{Key: "email", Value: email}}, user); err != nil {
|
||||||
|
err := fmt.Errorf("user %s was not retrievable from db during oauth authorization attempt: %s", email, err)
|
||||||
if err := m.db.GetWhere(ctx, []db.Where{{Key: "email", Value: email}}, gtsUser); err != nil {
|
return incorrectPassword(err)
|
||||||
l.Debugf("user %s was not retrievable from db during oauth authorization attempt: %s", email, err)
|
|
||||||
return incorrectPassword()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure a password is actually set and bail if not
|
if user.EncryptedPassword == "" {
|
||||||
if gtsUser.EncryptedPassword == "" {
|
err := fmt.Errorf("encrypted password for user %s was empty for some reason", user.Email)
|
||||||
l.Warnf("encrypted password for user %s was empty for some reason", gtsUser.Email)
|
return incorrectPassword(err)
|
||||||
return incorrectPassword()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare the provided password with the encrypted one from the db, bail if they don't match
|
if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(password)); err != nil {
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(gtsUser.EncryptedPassword), []byte(password)); err != nil {
|
err := fmt.Errorf("password hash didn't match for user %s during login attempt: %s", user.Email, err)
|
||||||
l.Debugf("password hash didn't match for user %s during login attempt: %s", gtsUser.Email, err)
|
return incorrectPassword(err)
|
||||||
return incorrectPassword()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've made it this far the email/password is correct, so we can just return the id of the user.
|
return user.ID, nil
|
||||||
userid = gtsUser.ID
|
|
||||||
l.Tracef("returning (%s, %s)", userid, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// incorrectPassword is just a little helper function to use in the ValidatePassword function
|
// incorrectPassword wraps the given error in a gtserror.WithCode, and returns
|
||||||
func incorrectPassword() (string, error) {
|
// only a generic 'safe' error message to the user, to not give any info away.
|
||||||
return "", errors.New("password/email combination was incorrect")
|
func incorrectPassword(err error) (string, gtserror.WithCode) {
|
||||||
|
safeErr := fmt.Errorf("password/email combination was incorrect")
|
||||||
|
return "", gtserror.NewErrorUnauthorized(err, safeErr.Error(), helpfulAdvice)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,10 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
@ -40,16 +39,17 @@ type tokenBody struct {
|
||||||
// TokenPOSTHandler should be served as a POST at https://example.org/oauth/token
|
// TokenPOSTHandler should be served as a POST at https://example.org/oauth/token
|
||||||
// The idea here is to serve an oauth access token to a user, which can be used for authorizing against non-public APIs.
|
// The idea here is to serve an oauth access token to a user, which can be used for authorizing against non-public APIs.
|
||||||
func (m *Module) TokenPOSTHandler(c *gin.Context) {
|
func (m *Module) TokenPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "TokenPOSTHandler")
|
|
||||||
l.Trace("entered TokenPOSTHandler")
|
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form := &tokenBody{}
|
form := &tokenBody{}
|
||||||
if err := c.ShouldBind(form); err == nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Request.Form = url.Values{}
|
c.Request.Form = url.Values{}
|
||||||
if form.ClientID != nil {
|
if form.ClientID != nil {
|
||||||
c.Request.Form.Set("client_id", *form.ClientID)
|
c.Request.Form.Set("client_id", *form.ClientID)
|
||||||
|
@ -69,9 +69,10 @@ func (m *Module) TokenPOSTHandler(c *gin.Context) {
|
||||||
if form.Scope != nil {
|
if form.Scope != nil {
|
||||||
c.Request.Form.Set("scope", *form.Scope)
|
c.Request.Form.Set("scope", *form.Scope)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// pass the writer and request into the oauth server handler, which will
|
||||||
|
// take care of writing the oauth token into the response etc
|
||||||
if err := m.server.HandleTokenRequest(c.Writer, c.Request); err != nil {
|
if err := m.server.HandleTokenRequest(c.Writer, c.Request); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err, helpfulAdvice), m.processor.InstanceGet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,13 @@
|
||||||
package blocks
|
package blocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -80,24 +80,25 @@ import (
|
||||||
// type: array
|
// type: array
|
||||||
// items:
|
// items:
|
||||||
// "$ref": "#/definitions/account"
|
// "$ref": "#/definitions/account"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) BlocksGETHandler(c *gin.Context) {
|
func (m *Module) BlocksGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "PublicTimelineGETHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error authing: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,8 +119,8 @@ func (m *Module) BlocksGETHandler(c *gin.Context) {
|
||||||
if limitString != "" {
|
if limitString != "" {
|
||||||
i, err := strconv.ParseInt(limitString, 10, 64)
|
i, err := strconv.ParseInt(limitString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing limit string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", LimitKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit = int(i)
|
limit = int(i)
|
||||||
|
@ -127,8 +128,7 @@ func (m *Module) BlocksGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
resp, errWithCode := m.processor.BlocksGet(c.Request.Context(), authed, maxID, sinceID, limit)
|
resp, errWithCode := m.processor.BlocksGet(c.Request.Context(), authed, maxID, sinceID, limit)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error from processor BlocksGet: %s", errWithCode)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,25 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EmojisGETHandler returns a list of custom emojis enabled on the instance
|
// EmojisGETHandler returns a list of custom emojis enabled on the instance
|
||||||
func (m *Module) EmojisGETHandler(c *gin.Context) {
|
func (m *Module) EmojisGETHandler(c *gin.Context) {
|
||||||
|
if _, err := oauth.Authed(c, true, true, true, true); err != nil {
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
emojis, errWithCode := m.processor.CustomEmojisGet(c)
|
emojis, errWithCode := m.processor.CustomEmojisGet(c)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
package favourites
|
package favourites
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FavouritesGETHandler handles GETting favourites.
|
// FavouritesGETHandler handles GETting favourites.
|
||||||
func (m *Module) FavouritesGETHandler(c *gin.Context) {
|
func (m *Module) FavouritesGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "PublicTimelineGETHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error authing: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +41,8 @@ func (m *Module) FavouritesGETHandler(c *gin.Context) {
|
||||||
if limitString != "" {
|
if limitString != "" {
|
||||||
i, err := strconv.ParseInt(limitString, 10, 64)
|
i, err := strconv.ParseInt(limitString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing limit string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", LimitKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit = int(i)
|
limit = int(i)
|
||||||
|
@ -53,8 +50,7 @@ func (m *Module) FavouritesGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
resp, errWithCode := m.processor.FavedTimelineGet(c.Request.Context(), authed, maxID, minID, limit)
|
resp, errWithCode := m.processor.FavedTimelineGet(c.Request.Context(), authed, maxID, minID, limit)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error from processor FavedTimelineGet: %s", errWithCode)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package fileserver
|
package fileserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,17 +36,9 @@ import (
|
||||||
// Note: to mitigate scraping attempts, no information should be given out on a bad request except "404 page not found".
|
// Note: to mitigate scraping attempts, no information should be given out on a bad request except "404 page not found".
|
||||||
// Don't give away account ids or media ids or anything like that; callers shouldn't be able to infer anything.
|
// Don't give away account ids or media ids or anything like that; callers shouldn't be able to infer anything.
|
||||||
func (m *FileServer) ServeFile(c *gin.Context) {
|
func (m *FileServer) ServeFile(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "ServeFile",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Trace("received request")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, false, false, false, false)
|
authed, err := oauth.Authed(c, false, false, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,29 +47,29 @@ func (m *FileServer) ServeFile(c *gin.Context) {
|
||||||
// "FILE_NAME" consists of two parts, the attachment's database id, a period, and the file extension.
|
// "FILE_NAME" consists of two parts, the attachment's database id, a period, and the file extension.
|
||||||
accountID := c.Param(AccountIDKey)
|
accountID := c.Param(AccountIDKey)
|
||||||
if accountID == "" {
|
if accountID == "" {
|
||||||
l.Debug("missing accountID from request")
|
err := fmt.Errorf("missing %s from request", AccountIDKey)
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaType := c.Param(MediaTypeKey)
|
mediaType := c.Param(MediaTypeKey)
|
||||||
if mediaType == "" {
|
if mediaType == "" {
|
||||||
l.Debug("missing mediaType from request")
|
err := fmt.Errorf("missing %s from request", MediaTypeKey)
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaSize := c.Param(MediaSizeKey)
|
mediaSize := c.Param(MediaSizeKey)
|
||||||
if mediaSize == "" {
|
if mediaSize == "" {
|
||||||
l.Debug("missing mediaSize from request")
|
err := fmt.Errorf("missing %s from request", MediaSizeKey)
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := c.Param(FileNameKey)
|
fileName := c.Param(FileNameKey)
|
||||||
if fileName == "" {
|
if fileName == "" {
|
||||||
l.Debug("missing fileName from request")
|
err := fmt.Errorf("missing %s from request", FileNameKey)
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,8 +80,7 @@ func (m *FileServer) ServeFile(c *gin.Context) {
|
||||||
FileName: fileName,
|
FileName: fileName,
|
||||||
})
|
})
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Errorf(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +88,7 @@ func (m *FileServer) ServeFile(c *gin.Context) {
|
||||||
// if the content is a ReadCloser, close it when we're done
|
// if the content is a ReadCloser, close it when we're done
|
||||||
if closer, ok := content.Content.(io.ReadCloser); ok {
|
if closer, ok := content.Content.(io.ReadCloser); ok {
|
||||||
if err := closer.Close(); err != nil {
|
if err := closer.Close(); err != nil {
|
||||||
l.Errorf("error closing readcloser: %s", err)
|
logrus.Errorf("ServeFile: error closing readcloser: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -103,9 +96,9 @@ func (m *FileServer) ServeFile(c *gin.Context) {
|
||||||
// TODO: if the requester only accepts text/html we should try to serve them *something*.
|
// TODO: if the requester only accepts text/html we should try to serve them *something*.
|
||||||
// This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will
|
// This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will
|
||||||
// attempt to look up the content to provide a preview of the link, and they ask for text/html.
|
// attempt to look up the content to provide a preview of the link, and they ask for text/html.
|
||||||
format, err := api.NegotiateAccept(c, api.Offer(content.ContentType))
|
format, err := api.NegotiateAccept(c, api.MIME(content.ContentType))
|
||||||
if errWithCode != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,19 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FiltersGETHandler returns a list of filters set by/for the authed account
|
// FiltersGETHandler returns a list of filters set by/for the authed account
|
||||||
func (m *Module) FiltersGETHandler(c *gin.Context) {
|
func (m *Module) FiltersGETHandler(c *gin.Context) {
|
||||||
|
if _, err := oauth.Authed(c, true, true, true, true); err != nil {
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,12 @@
|
||||||
package followrequest
|
package followrequest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,43 +62,34 @@ import (
|
||||||
// description: bad request
|
// description: bad request
|
||||||
// '401':
|
// '401':
|
||||||
// description: unauthorized
|
// description: unauthorized
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
// '500':
|
// '500':
|
||||||
// description: internal server error
|
// description: internal server error
|
||||||
func (m *Module) FollowRequestAuthorizePOSTHandler(c *gin.Context) {
|
func (m *Module) FollowRequestAuthorizePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "FollowRequestAuthorizePOSTHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() {
|
|
||||||
l.Debugf("couldn't auth: %s", err)
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
originAccountID := c.Param(IDKey)
|
originAccountID := c.Param(IDKey)
|
||||||
if originAccountID == "" {
|
if originAccountID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no follow request origin account id provided"})
|
err := errors.New("no account id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
relationship, errWithCode := m.processor.FollowRequestAccept(c.Request.Context(), authed, originAccountID)
|
relationship, errWithCode := m.processor.FollowRequestAccept(c.Request.Context(), authed, originAccountID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debug(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,34 @@ func (suite *AuthorizeTestSuite) TestAuthorize() {
|
||||||
suite.Equal(`{"id":"01FHMQX3GAABWSM0S2VZEC2SWC","following":false,"showing_reblogs":false,"notifying":false,"followed_by":true,"blocking":false,"blocked_by":false,"muting":false,"muting_notifications":false,"requested":false,"domain_blocking":false,"endorsed":false,"note":""}`, string(b))
|
suite.Equal(`{"id":"01FHMQX3GAABWSM0S2VZEC2SWC","following":false,"showing_reblogs":false,"notifying":false,"followed_by":true,"blocking":false,"blocked_by":false,"muting":false,"muting_notifications":false,"requested":false,"domain_blocking":false,"endorsed":false,"note":""}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *AuthorizeTestSuite) TestAuthorizeNoFR() {
|
||||||
|
requestingAccount := suite.testAccounts["remote_account_2"]
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
ctx := suite.newContext(recorder, http.MethodPost, []byte{}, fmt.Sprintf("/api/v1/follow_requests/%s/authorize", requestingAccount.ID), "")
|
||||||
|
|
||||||
|
ctx.Params = gin.Params{
|
||||||
|
gin.Param{
|
||||||
|
Key: followrequest.IDKey,
|
||||||
|
Value: requestingAccount.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// call the handler
|
||||||
|
suite.followRequestModule.FollowRequestAuthorizePOSTHandler(ctx)
|
||||||
|
|
||||||
|
suite.Equal(http.StatusNotFound, recorder.Code)
|
||||||
|
|
||||||
|
result := recorder.Result()
|
||||||
|
defer result.Body.Close()
|
||||||
|
|
||||||
|
// check the response
|
||||||
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
|
assert.NoError(suite.T(), err)
|
||||||
|
|
||||||
|
suite.Equal(`{"error":"Not Found"}`, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuthorizeTestSuite(t *testing.T) {
|
func TestAuthorizeTestSuite(t *testing.T) {
|
||||||
suite.Run(t, &AuthorizeTestSuite{})
|
suite.Run(t, &AuthorizeTestSuite{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,9 @@ package followrequest
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,34 +70,27 @@ import (
|
||||||
// description: bad request
|
// description: bad request
|
||||||
// '401':
|
// '401':
|
||||||
// description: unauthorized
|
// description: unauthorized
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) FollowRequestGETHandler(c *gin.Context) {
|
func (m *Module) FollowRequestGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "FollowRequestGETHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() {
|
|
||||||
l.Debugf("couldn't auth: %s", err)
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
accts, errWithCode := m.processor.FollowRequestsGet(c.Request.Context(), authed)
|
accts, errWithCode := m.processor.FollowRequestsGet(c.Request.Context(), authed)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package followrequest
|
package followrequest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,43 +60,34 @@ import (
|
||||||
// description: bad request
|
// description: bad request
|
||||||
// '401':
|
// '401':
|
||||||
// description: unauthorized
|
// description: unauthorized
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
// '500':
|
// '500':
|
||||||
// description: internal server error
|
// description: internal server error
|
||||||
func (m *Module) FollowRequestRejectPOSTHandler(c *gin.Context) {
|
func (m *Module) FollowRequestRejectPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "FollowRequestRejectPOSTHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() {
|
|
||||||
l.Debugf("couldn't auth: %s", err)
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
originAccountID := c.Param(IDKey)
|
originAccountID := c.Param(IDKey)
|
||||||
if originAccountID == "" {
|
if originAccountID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no follow request origin account id provided"})
|
err := errors.New("no account id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
relationship, errWithCode := m.processor.FollowRequestReject(c.Request.Context(), authed, originAccountID)
|
relationship, errWithCode := m.processor.FollowRequestReject(c.Request.Context(), authed, originAccountID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debug(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ package instance
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
@ -30,22 +30,19 @@ import (
|
||||||
// description: "Instance information."
|
// description: "Instance information."
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/instance"
|
// "$ref": "#/definitions/instance"
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
// '500':
|
// '500':
|
||||||
// description: internal error
|
// description: internal error
|
||||||
func (m *Module) InstanceInformationGETHandler(c *gin.Context) {
|
func (m *Module) InstanceInformationGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "InstanceInformationGETHandler")
|
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
host := config.GetHost()
|
instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost())
|
||||||
|
if errWithCode != nil {
|
||||||
instance, err := m.processor.InstanceGet(c.Request.Context(), host)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
if err != nil {
|
|
||||||
l.Debugf("error getting instance from processor: %s", err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package instance
|
package instance
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,52 +82,51 @@ import (
|
||||||
// description: "The newly updated instance."
|
// description: "The newly updated instance."
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/instance"
|
// "$ref": "#/definitions/instance"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) InstanceUpdatePATCHHandler(c *gin.Context) {
|
func (m *Module) InstanceUpdatePATCHHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "InstanceUpdatePATCHHandler")
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// only admins can update instance settings
|
|
||||||
if !authed.User.Admin {
|
if !authed.User.Admin {
|
||||||
l.Debug("user is not an admin so cannot update instance settings")
|
err := errors.New("user is not an admin so cannot update instance settings")
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not an admin"})
|
api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Debug("parsing request form")
|
|
||||||
form := &model.InstanceSettingsUpdateRequest{}
|
form := &model.InstanceSettingsUpdateRequest{}
|
||||||
if err := c.ShouldBind(&form); err != nil || form == nil {
|
if err := c.ShouldBind(&form); err != nil {
|
||||||
l.Debugf("could not parse form from request: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Debugf("parsed form: %+v", form)
|
|
||||||
|
|
||||||
// if everything on the form is nil, then nothing has been set and we shouldn't continue
|
|
||||||
if form.Title == nil && form.ContactUsername == nil && form.ContactEmail == nil && form.ShortDescription == nil && form.Description == nil && form.Terms == nil && form.Avatar == nil && form.Header == nil {
|
if form.Title == nil && form.ContactUsername == nil && form.ContactEmail == nil && form.ShortDescription == nil && form.Description == nil && form.Terms == nil && form.Avatar == nil && form.Header == nil {
|
||||||
l.Debugf("could not parse form from request")
|
err := errors.New("empty form submitted")
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "empty form submitted"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
i, errWithCode := m.processor.InstancePatch(c.Request.Context(), form)
|
i, errWithCode := m.processor.InstancePatch(c.Request.Context(), form)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error with instance patch request: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/instance"
|
"github.com/superseriousbusiness/gotosocial/internal/api/client/instance"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -125,6 +126,67 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
|
||||||
suite.Equal(`{"uri":"http://localhost:8080","title":"localhost:8080","description":"","short_description":"\u003cp\u003eThis is some html, which is \u003cem\u003eallowed\u003c/em\u003e in short descriptions.\u003c/p\u003e","email":"","version":"","registrations":true,"approval_required":true,"invites_enabled":false,"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":0,"status_count":16,"user_count":4},"thumbnail":"","max_toot_chars":5000}`, string(b))
|
suite.Equal(`{"uri":"http://localhost:8080","title":"localhost:8080","description":"","short_description":"\u003cp\u003eThis is some html, which is \u003cem\u003eallowed\u003c/em\u003e in short descriptions.\u003c/p\u003e","email":"","version":"","registrations":true,"approval_required":true,"invites_enabled":false,"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":0,"status_count":16,"user_count":4},"thumbnail":"","max_toot_chars":5000}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *InstancePatchTestSuite) TestInstancePatch4() {
|
||||||
|
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||||
|
"", "",
|
||||||
|
map[string]string{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
bodyBytes := requestBody.Bytes()
|
||||||
|
|
||||||
|
// set up the request
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType())
|
||||||
|
|
||||||
|
// call the handler
|
||||||
|
suite.instanceModule.InstanceUpdatePATCHHandler(ctx)
|
||||||
|
|
||||||
|
suite.Equal(http.StatusBadRequest, recorder.Code)
|
||||||
|
|
||||||
|
result := recorder.Result()
|
||||||
|
defer result.Body.Close()
|
||||||
|
|
||||||
|
b, err := io.ReadAll(result.Body)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.Equal(`{"error":"Bad Request: empty form submitted"}`, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *InstancePatchTestSuite) TestInstancePatch5() {
|
||||||
|
requestBody, w, err := testrig.CreateMultipartFormData(
|
||||||
|
"", "",
|
||||||
|
map[string]string{
|
||||||
|
"short_description": "<p>This is some html, which is <em>allowed</em> in short descriptions.</p>",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
bodyBytes := requestBody.Bytes()
|
||||||
|
|
||||||
|
// set up the request
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType())
|
||||||
|
|
||||||
|
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||||
|
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||||
|
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||||
|
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||||
|
|
||||||
|
// call the handler
|
||||||
|
suite.instanceModule.InstanceUpdatePATCHHandler(ctx)
|
||||||
|
|
||||||
|
suite.Equal(http.StatusForbidden, recorder.Code)
|
||||||
|
|
||||||
|
result := recorder.Result()
|
||||||
|
defer result.Body.Close()
|
||||||
|
|
||||||
|
b, err := io.ReadAll(result.Body)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.Equal(`{"error":"Forbidden: user is not an admin so cannot update instance settings"}`, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
func TestInstancePatchTestSuite(t *testing.T) {
|
func TestInstancePatchTestSuite(t *testing.T) {
|
||||||
suite.Run(t, &InstancePatchTestSuite{})
|
suite.Run(t, &InstancePatchTestSuite{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,19 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListsGETHandler returns a list of lists created by/for the authed account
|
// ListsGETHandler returns a list of lists created by/for the authed account
|
||||||
func (m *Module) ListsGETHandler(c *gin.Context) {
|
func (m *Module) ListsGETHandler(c *gin.Context) {
|
||||||
|
if _, err := oauth.Authed(c, true, true, true, true); err != nil {
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -80,46 +79,36 @@ import (
|
||||||
// description: bad request
|
// description: bad request
|
||||||
// '401':
|
// '401':
|
||||||
// description: unauthorized
|
// description: unauthorized
|
||||||
// '403':
|
|
||||||
// description: forbidden
|
|
||||||
// '422':
|
// '422':
|
||||||
// description: unprocessable
|
// description: unprocessable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {
|
func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "statusCreatePOSTHandler")
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
authed, err := oauth.Authed(c, true, true, true, true) // posting new media is serious business so we want *everything*
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the media create form from the request context
|
|
||||||
l.Tracef("parsing request form: %s", c.Request.Form)
|
|
||||||
form := &model.AttachmentRequest{}
|
form := &model.AttachmentRequest{}
|
||||||
if err := c.ShouldBind(&form); err != nil {
|
if err := c.ShouldBind(&form); err != nil {
|
||||||
l.Debugf("error parsing form: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("could not parse form: %s", err)})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give the fields on the request form a first pass to make sure the request is superficially valid.
|
|
||||||
l.Tracef("validating form %+v", form)
|
|
||||||
if err := validateCreateMedia(form); err != nil {
|
if err := validateCreateMedia(form); err != nil {
|
||||||
l.Debugf("error validating form: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Debug("calling processor media create func")
|
apiAttachment, errWithCode := m.processor.MediaCreate(c.Request.Context(), authed, form)
|
||||||
apiAttachment, err := m.processor.MediaCreate(c.Request.Context(), authed, form)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error creating attachment: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,6 +132,7 @@ func validateCreateMedia(form *model.AttachmentRequest) error {
|
||||||
if maxImageSize > maxSize {
|
if maxImageSize > maxSize {
|
||||||
maxSize = maxImageSize
|
maxSize = maxImageSize
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.File.Size > int64(maxSize) {
|
if form.File.Size > int64(maxSize) {
|
||||||
return fmt.Errorf("file size limit exceeded: limit is %d bytes but attachment was %d bytes", maxSize, form.File.Size)
|
return fmt.Errorf("file size limit exceeded: limit is %d bytes but attachment was %d bytes", maxSize, form.File.Size)
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,15 +247,14 @@ func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() {
|
||||||
suite.mediaModule.MediaCreatePOSTHandler(ctx)
|
suite.mediaModule.MediaCreatePOSTHandler(ctx)
|
||||||
|
|
||||||
// check response
|
// check response
|
||||||
suite.EqualValues(http.StatusUnprocessableEntity, recorder.Code)
|
suite.EqualValues(http.StatusBadRequest, recorder.Code)
|
||||||
|
|
||||||
result := recorder.Result()
|
result := recorder.Result()
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
expectedErr := fmt.Sprintf(`{"error":"image description length must be between 0 and 500 characters (inclusive), but provided image description was %d chars"}`, len(description))
|
suite.Equal(`{"error":"Bad Request: image description length must be between 0 and 500 characters (inclusive), but provided image description was 6667 chars"}`, string(b))
|
||||||
suite.Equal(expectedErr, string(b))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() {
|
func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() {
|
||||||
|
|
|
@ -19,12 +19,12 @@
|
||||||
package media
|
package media
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,33 +59,34 @@ import (
|
||||||
// description: bad request
|
// description: bad request
|
||||||
// '401':
|
// '401':
|
||||||
// description: unauthorized
|
// description: unauthorized
|
||||||
// '403':
|
// '404':
|
||||||
// description: forbidden
|
// description: not found
|
||||||
// '422':
|
// '406':
|
||||||
// description: unprocessable
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) MediaGETHandler(c *gin.Context) {
|
func (m *Module) MediaGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "MediaGETHandler")
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
attachmentID := c.Param(IDKey)
|
attachmentID := c.Param(IDKey)
|
||||||
if attachmentID == "" {
|
if attachmentID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no attachment ID given in request"})
|
err := errors.New("no attachment id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment, errWithCode := m.processor.MediaGet(c.Request.Context(), authed, attachmentID)
|
attachment, errWithCode := m.processor.MediaGet(c.Request.Context(), authed, attachmentID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,50 +88,45 @@ import (
|
||||||
// description: bad request
|
// description: bad request
|
||||||
// '401':
|
// '401':
|
||||||
// description: unauthorized
|
// description: unauthorized
|
||||||
// '403':
|
// '404':
|
||||||
// description: forbidden
|
// description: not found
|
||||||
// '422':
|
// '406':
|
||||||
// description: unprocessable
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) MediaPUTHandler(c *gin.Context) {
|
func (m *Module) MediaPUTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "MediaGETHandler")
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
attachmentID := c.Param(IDKey)
|
attachmentID := c.Param(IDKey)
|
||||||
if attachmentID == "" {
|
if attachmentID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no attachment ID given in request"})
|
err := errors.New("no attachment id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the media update form from the request context
|
form := &model.AttachmentUpdateRequest{}
|
||||||
l.Tracef("parsing request form: %s", c.Request.Form)
|
if err := c.ShouldBind(form); err != nil {
|
||||||
var form model.AttachmentUpdateRequest
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
if err := c.ShouldBind(&form); err != nil {
|
|
||||||
l.Debugf("could not parse form from request: %s", err)
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "missing one or more required form values"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give the fields on the request form a first pass to make sure the request is superficially valid.
|
if err := validateUpdateMedia(form); err != nil {
|
||||||
l.Tracef("validating form %+v", form)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
if err := validateUpdateMedia(&form); err != nil {
|
|
||||||
l.Debugf("error validating form: %s", err)
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment, errWithCode := m.processor.MediaUpdate(c.Request.Context(), authed, attachmentID, &form)
|
attachment, errWithCode := m.processor.MediaUpdate(c.Request.Context(), authed, attachmentID, form)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -232,7 +232,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImageShortDescription() {
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// reply should be an error message
|
// reply should be an error message
|
||||||
suite.Equal(`{"error":"image description length must be between 50 and 500 characters (inclusive), but provided image description was 16 chars"}`, string(b))
|
suite.Equal(`{"error":"Bad Request: image description length must be between 50 and 500 characters (inclusive), but provided image description was 16 chars"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaUpdateTestSuite(t *testing.T) {
|
func TestMediaUpdateTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -19,34 +19,26 @@
|
||||||
package notification
|
package notification
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NotificationsGETHandler serves a list of notifications to the caller, with the desired query parameters
|
// NotificationsGETHandler serves a list of notifications to the caller, with the desired query parameters
|
||||||
func (m *Module) NotificationsGETHandler(c *gin.Context) {
|
func (m *Module) NotificationsGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
"func": "NotificationsGETHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true) // we don't really need an app here but we want everything else
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("error authing status faved by request: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "not authed"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +47,8 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) {
|
||||||
if limitString != "" {
|
if limitString != "" {
|
||||||
i, err := strconv.ParseInt(limitString, 10, 64)
|
i, err := strconv.ParseInt(limitString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing limit string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", LimitKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit = int(i)
|
limit = int(i)
|
||||||
|
@ -76,8 +68,7 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
resp, errWithCode := m.processor.NotificationsGet(c.Request.Context(), authed, limit, maxID, sinceID)
|
resp, errWithCode := m.processor.NotificationsGet(c.Request.Context(), authed, limit, maxID, sinceID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing notifications get: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,15 @@
|
||||||
package search
|
package search
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,50 +53,44 @@ import (
|
||||||
// type: array
|
// type: array
|
||||||
// items:
|
// items:
|
||||||
// "$ref": "#/definitions/searchResult"
|
// "$ref": "#/definitions/searchResult"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) SearchGETHandler(c *gin.Context) {
|
func (m *Module) SearchGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
"func": "SearchGETHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true) // we don't really need an app here but we want everything else
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("error authing search request: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "not authed"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
accountID := c.Query(AccountIDKey)
|
|
||||||
maxID := c.Query(MaxIDKey)
|
|
||||||
minID := c.Query(MinIDKey)
|
|
||||||
searchType := c.Query(TypeKey)
|
|
||||||
|
|
||||||
excludeUnreviewed := false
|
excludeUnreviewed := false
|
||||||
excludeUnreviewedString := c.Query(ExcludeUnreviewedKey)
|
excludeUnreviewedString := c.Query(ExcludeUnreviewedKey)
|
||||||
if excludeUnreviewedString != "" {
|
if excludeUnreviewedString != "" {
|
||||||
var err error
|
var err error
|
||||||
excludeUnreviewed, err = strconv.ParseBool(excludeUnreviewedString)
|
excludeUnreviewed, err = strconv.ParseBool(excludeUnreviewedString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("couldn't parse param %s: %s", excludeUnreviewedString, err)})
|
err := fmt.Errorf("error parsing %s: %s", ExcludeUnreviewedKey, err)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query := c.Query(QueryKey)
|
query := c.Query(QueryKey)
|
||||||
if query == "" {
|
if query == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "query parameter q was empty"})
|
err := errors.New("query parameter q was empty")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,18 +100,19 @@ func (m *Module) SearchGETHandler(c *gin.Context) {
|
||||||
var err error
|
var err error
|
||||||
resolve, err = strconv.ParseBool(resolveString)
|
resolve, err = strconv.ParseBool(resolveString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("couldn't parse param %s: %s", resolveString, err)})
|
err := fmt.Errorf("error parsing %s: %s", ResolveKey, err)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
limit := 20
|
limit := 2
|
||||||
limitString := c.Query(LimitKey)
|
limitString := c.Query(LimitKey)
|
||||||
if limitString != "" {
|
if limitString != "" {
|
||||||
i, err := strconv.ParseInt(limitString, 10, 64)
|
i, err := strconv.ParseInt(limitString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing limit string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", LimitKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit = int(i)
|
limit = int(i)
|
||||||
|
@ -133,18 +129,12 @@ func (m *Module) SearchGETHandler(c *gin.Context) {
|
||||||
if offsetString != "" {
|
if offsetString != "" {
|
||||||
i, err := strconv.ParseInt(offsetString, 10, 64)
|
i, err := strconv.ParseInt(offsetString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing offset string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", OffsetKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse offset query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
offset = int(i)
|
offset = int(i)
|
||||||
}
|
}
|
||||||
if limit > 40 {
|
|
||||||
limit = 40
|
|
||||||
}
|
|
||||||
if limit < 1 {
|
|
||||||
limit = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
following := false
|
following := false
|
||||||
followingString := c.Query(FollowingKey)
|
followingString := c.Query(FollowingKey)
|
||||||
|
@ -152,16 +142,17 @@ func (m *Module) SearchGETHandler(c *gin.Context) {
|
||||||
var err error
|
var err error
|
||||||
following, err = strconv.ParseBool(followingString)
|
following, err = strconv.ParseBool(followingString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("couldn't parse param %s: %s", followingString, err)})
|
err := fmt.Errorf("error parsing %s: %s", FollowingKey, err)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
searchQuery := &model.SearchQuery{
|
searchQuery := &model.SearchQuery{
|
||||||
AccountID: accountID,
|
AccountID: c.Query(AccountIDKey),
|
||||||
MaxID: maxID,
|
MaxID: c.Query(MaxIDKey),
|
||||||
MinID: minID,
|
MinID: c.Query(MinIDKey),
|
||||||
Type: searchType,
|
Type: c.Query(TypeKey),
|
||||||
ExcludeUnreviewed: excludeUnreviewed,
|
ExcludeUnreviewed: excludeUnreviewed,
|
||||||
Query: query,
|
Query: query,
|
||||||
Resolve: resolve,
|
Resolve: resolve,
|
||||||
|
@ -172,8 +163,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
results, errWithCode := m.processor.SearchGet(c.Request.Context(), authed, searchQuery)
|
results, errWithCode := m.processor.SearchGet(c.Request.Context(), authed, searchQuery)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error searching: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -66,37 +67,32 @@ import (
|
||||||
// description: forbidden
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) StatusBoostPOSTHandler(c *gin.Context) {
|
func (m *Module) StatusBoostPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "StatusBoostPOSTHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debug("not authed so can't boost status")
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetStatusID := c.Param(IDKey)
|
targetStatusID := c.Param(IDKey)
|
||||||
if targetStatusID == "" {
|
if targetStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
|
err := errors.New("no status id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiStatus, errWithCode := m.processor.StatusBoost(c.Request.Context(), authed, targetStatusID)
|
apiStatus, errWithCode := m.processor.StatusBoost(c.Request.Context(), authed, targetStatusID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing status boost: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,13 +134,13 @@ func (suite *StatusBoostTestSuite) TestPostUnboostable() {
|
||||||
suite.statusModule.StatusBoostPOSTHandler(ctx)
|
suite.statusModule.StatusBoostPOSTHandler(ctx)
|
||||||
|
|
||||||
// check response
|
// check response
|
||||||
suite.EqualValues(http.StatusForbidden, recorder.Code) // we 403 unboostable statuses
|
suite.Equal(http.StatusForbidden, recorder.Code) // we 403 unboostable statuses
|
||||||
|
|
||||||
result := recorder.Result()
|
result := recorder.Result()
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
assert.NoError(suite.T(), err)
|
assert.NoError(suite.T(), err)
|
||||||
assert.Equal(suite.T(), `{"error":"forbidden"}`, string(b))
|
assert.Equal(suite.T(), `{"error":"Forbidden"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to boost a status that's not visible to the user
|
// try to boost a status that's not visible to the user
|
||||||
|
@ -177,13 +177,7 @@ func (suite *StatusBoostTestSuite) TestPostNotVisible() {
|
||||||
suite.statusModule.StatusBoostPOSTHandler(ctx)
|
suite.statusModule.StatusBoostPOSTHandler(ctx)
|
||||||
|
|
||||||
// check response
|
// check response
|
||||||
suite.EqualValues(http.StatusNotFound, recorder.Code) // we 404 statuses that aren't visible
|
suite.Equal(http.StatusNotFound, recorder.Code) // we 404 statuses that aren't visible
|
||||||
|
|
||||||
result := recorder.Result()
|
|
||||||
defer result.Body.Close()
|
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
|
||||||
assert.NoError(suite.T(), err)
|
|
||||||
assert.Equal(suite.T(), `{"error":"404 not found"}`, string(b))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatusBoostTestSuite(t *testing.T) {
|
func TestStatusBoostTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -84,10 +85,9 @@ func (m *Module) StatusBoostedByGETHandler(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiAccounts, err := m.processor.StatusBoostedBy(c.Request.Context(), authed, targetStatusID)
|
apiAccounts, errWithCode := m.processor.StatusBoostedBy(c.Request.Context(), authed, targetStatusID)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing status boosted by request: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,37 +66,32 @@ import (
|
||||||
// description: forbidden
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) StatusContextGETHandler(c *gin.Context) {
|
func (m *Module) StatusContextGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "StatusContextGETHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("error authing status context request: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "not authed"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetStatusID := c.Param(IDKey)
|
targetStatusID := c.Param(IDKey)
|
||||||
if targetStatusID == "" {
|
if targetStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
|
err := errors.New("no status id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
statusContext, errWithCode := m.processor.StatusGetContext(c.Request.Context(), authed, targetStatusID)
|
statusContext, errWithCode := m.processor.StatusGetContext(c.Request.Context(), authed, targetStatusID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error getting status context: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||||
)
|
)
|
||||||
|
@ -61,58 +60,44 @@ import (
|
||||||
// description: "The newly created status."
|
// description: "The newly created status."
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/status"
|
// "$ref": "#/definitions/status"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
// '500':
|
// '500':
|
||||||
// description: internal error
|
// description: internal server error
|
||||||
func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "statusCreatePOSTHandler")
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("couldn't auth: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// First check this user/account is permitted to post new statuses.
|
|
||||||
// There's no point continuing otherwise.
|
|
||||||
if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() {
|
|
||||||
l.Debugf("couldn't auth: %s", err)
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract the status create form from the request context
|
|
||||||
l.Debugf("parsing request form: %s", c.Request.Form)
|
|
||||||
form := &model.AdvancedStatusCreateForm{}
|
form := &model.AdvancedStatusCreateForm{}
|
||||||
if err := c.ShouldBind(form); err != nil || form == nil {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
l.Debugf("could not parse form from request: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "missing one or more required form values"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Debugf("handling status request form: %+v", form)
|
|
||||||
|
|
||||||
// Give the fields on the request form a first pass to make sure the request is superficially valid.
|
|
||||||
l.Tracef("validating form %+v", form)
|
|
||||||
if err := validateCreateStatus(form); err != nil {
|
if err := validateCreateStatus(form); err != nil {
|
||||||
l.Debugf("error validating form: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiStatus, err := m.processor.StatusCreate(c.Request.Context(), authed, form)
|
apiStatus, errWithCode := m.processor.StatusCreate(c.Request.Context(), authed, form)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing status create: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +105,6 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCreateStatus(form *model.AdvancedStatusCreateForm) error {
|
func validateCreateStatus(form *model.AdvancedStatusCreateForm) error {
|
||||||
// validate that, structurally, we have a valid status/post
|
|
||||||
if form.Status == "" && form.MediaIDs == nil && form.Poll == nil {
|
if form.Status == "" && form.MediaIDs == nil && form.Poll == nil {
|
||||||
return errors.New("no status, media, or poll provided")
|
return errors.New("no status, media, or poll provided")
|
||||||
}
|
}
|
||||||
|
@ -135,19 +119,16 @@ func validateCreateStatus(form *model.AdvancedStatusCreateForm) error {
|
||||||
maxPollChars := config.GetStatusesPollOptionMaxChars()
|
maxPollChars := config.GetStatusesPollOptionMaxChars()
|
||||||
maxCwChars := config.GetStatusesCWMaxChars()
|
maxCwChars := config.GetStatusesCWMaxChars()
|
||||||
|
|
||||||
// validate status
|
|
||||||
if form.Status != "" {
|
if form.Status != "" {
|
||||||
if len(form.Status) > maxChars {
|
if len(form.Status) > maxChars {
|
||||||
return fmt.Errorf("status too long, %d characters provided but limit is %d", len(form.Status), maxChars)
|
return fmt.Errorf("status too long, %d characters provided but limit is %d", len(form.Status), maxChars)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate media attachments
|
|
||||||
if len(form.MediaIDs) > maxMediaFiles {
|
if len(form.MediaIDs) > maxMediaFiles {
|
||||||
return fmt.Errorf("too many media files attached to status, %d attached but limit is %d", len(form.MediaIDs), maxMediaFiles)
|
return fmt.Errorf("too many media files attached to status, %d attached but limit is %d", len(form.MediaIDs), maxMediaFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate poll
|
|
||||||
if form.Poll != nil {
|
if form.Poll != nil {
|
||||||
if form.Poll.Options == nil {
|
if form.Poll.Options == nil {
|
||||||
return errors.New("poll with no options")
|
return errors.New("poll with no options")
|
||||||
|
@ -162,14 +143,12 @@ func validateCreateStatus(form *model.AdvancedStatusCreateForm) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate spoiler text/cw
|
|
||||||
if form.SpoilerText != "" {
|
if form.SpoilerText != "" {
|
||||||
if len(form.SpoilerText) > maxCwChars {
|
if len(form.SpoilerText) > maxCwChars {
|
||||||
return fmt.Errorf("content-warning/spoilertext too long, %d characters provided but limit is %d", len(form.SpoilerText), maxCwChars)
|
return fmt.Errorf("content-warning/spoilertext too long, %d characters provided but limit is %d", len(form.SpoilerText), maxCwChars)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate post language
|
|
||||||
if form.Language != "" {
|
if form.Language != "" {
|
||||||
if err := validate.Language(form.Language); err != nil {
|
if err := validate.Language(form.Language); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -256,7 +256,7 @@ func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() {
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(`{"error":"bad request"}`, string(b))
|
suite.Equal(`{"error":"Bad Request: status with id 3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50 not replyable because it doesn't exist"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post a reply to the status of a local user that allows replies.
|
// Post a reply to the status of a local user that allows replies.
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,43 +66,32 @@ import (
|
||||||
// description: forbidden
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) StatusDELETEHandler(c *gin.Context) {
|
func (m *Module) StatusDELETEHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "StatusDELETEHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debug("not authed so can't delete status")
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetStatusID := c.Param(IDKey)
|
targetStatusID := c.Param(IDKey)
|
||||||
if targetStatusID == "" {
|
if targetStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
|
err := errors.New("no status id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiStatus, err := m.processor.StatusDelete(c.Request.Context(), authed, targetStatusID)
|
apiStatus, errWithCode := m.processor.StatusDelete(c.Request.Context(), authed, targetStatusID)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing status delete: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// the status was already gone/never existed
|
|
||||||
if apiStatus == nil {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,37 +63,32 @@ import (
|
||||||
// description: forbidden
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) StatusFavePOSTHandler(c *gin.Context) {
|
func (m *Module) StatusFavePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "StatusFavePOSTHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debug("not authed so can't fave status")
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetStatusID := c.Param(IDKey)
|
targetStatusID := c.Param(IDKey)
|
||||||
if targetStatusID == "" {
|
if targetStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
|
err := errors.New("no status id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiStatus, err := m.processor.StatusFave(c.Request.Context(), authed, targetStatusID)
|
apiStatus, errWithCode := m.processor.StatusFave(c.Request.Context(), authed, targetStatusID)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing status fave: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,13 +118,13 @@ func (suite *StatusFaveTestSuite) TestPostUnfaveable() {
|
||||||
suite.statusModule.StatusFavePOSTHandler(ctx)
|
suite.statusModule.StatusFavePOSTHandler(ctx)
|
||||||
|
|
||||||
// check response
|
// check response
|
||||||
suite.EqualValues(http.StatusBadRequest, recorder.Code)
|
suite.EqualValues(http.StatusForbidden, recorder.Code)
|
||||||
|
|
||||||
result := recorder.Result()
|
result := recorder.Result()
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
assert.NoError(suite.T(), err)
|
assert.NoError(suite.T(), err)
|
||||||
assert.Equal(suite.T(), `{"error":"bad request"}`, string(b))
|
assert.Equal(suite.T(), `{"error":"Forbidden"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatusFaveTestSuite(t *testing.T) {
|
func TestStatusFaveTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,37 +64,32 @@ import (
|
||||||
// description: forbidden
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) StatusFavedByGETHandler(c *gin.Context) {
|
func (m *Module) StatusFavedByGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
"func": "statusGETHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true) // we don't really need an app here but we want everything else
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("error authing status faved by request: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "not authed"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetStatusID := c.Param(IDKey)
|
targetStatusID := c.Param(IDKey)
|
||||||
if targetStatusID == "" {
|
if targetStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
|
err := errors.New("no status id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiAccounts, err := m.processor.StatusFavedBy(c.Request.Context(), authed, targetStatusID)
|
apiAccounts, errWithCode := m.processor.StatusFavedBy(c.Request.Context(), authed, targetStatusID)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing status faved by request: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,45 +55,40 @@ import (
|
||||||
// description: "The requested created status."
|
// description: "The requested created status."
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/status"
|
// "$ref": "#/definitions/status"
|
||||||
// '401':
|
|
||||||
// description: unauthorized
|
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
// '500':
|
// '500':
|
||||||
// description: internal error
|
// description: internal server error
|
||||||
func (m *Module) StatusGETHandler(c *gin.Context) {
|
func (m *Module) StatusGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
"func": "statusGETHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, false, false, false, false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("error authing status faved by request: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "not authed"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetStatusID := c.Param(IDKey)
|
targetStatusID := c.Param(IDKey)
|
||||||
if targetStatusID == "" {
|
if targetStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
|
err := errors.New("no status id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiStatus, err := m.processor.StatusGet(c.Request.Context(), authed, targetStatusID)
|
apiStatus, errWithCode := m.processor.StatusGet(c.Request.Context(), authed, targetStatusID)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing status get: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,37 +64,32 @@ import (
|
||||||
// description: forbidden
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) StatusUnboostPOSTHandler(c *gin.Context) {
|
func (m *Module) StatusUnboostPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "StatusUnboostPOSTHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debug("not authed so can't unboost status")
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetStatusID := c.Param(IDKey)
|
targetStatusID := c.Param(IDKey)
|
||||||
if targetStatusID == "" {
|
if targetStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
|
err := errors.New("no status id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiStatus, errWithCode := m.processor.StatusUnboost(c.Request.Context(), authed, targetStatusID)
|
apiStatus, errWithCode := m.processor.StatusUnboost(c.Request.Context(), authed, targetStatusID)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing status unboost: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,37 +63,32 @@ import (
|
||||||
// description: forbidden
|
// description: forbidden
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
func (m *Module) StatusUnfavePOSTHandler(c *gin.Context) {
|
func (m *Module) StatusUnfavePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "StatusUnfavePOSTHandler",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
l.Debugf("entering function")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debug("not authed so can't unfave status")
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetStatusID := c.Param(IDKey)
|
targetStatusID := c.Param(IDKey)
|
||||||
if targetStatusID == "" {
|
if targetStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
|
err := errors.New("no status id specified")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiStatus, err := m.processor.StatusUnfave(c.Request.Context(), authed, targetStatusID)
|
apiStatus, errWithCode := m.processor.StatusUnfave(c.Request.Context(), authed, targetStatusID)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error processing status unfave: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,24 @@ package streaming
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var wsUpgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
// we expect cors requests (via eg., pinafore.social) so be lenient
|
||||||
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
|
}
|
||||||
|
|
||||||
// StreamGETHandler swagger:operation GET /api/v1/streaming streamGet
|
// StreamGETHandler swagger:operation GET /api/v1/streaming streamGet
|
||||||
//
|
//
|
||||||
// Initiate a websocket connection for live streaming of statuses and notifications.
|
// Initiate a websocket connection for live streaming of statuses and notifications.
|
||||||
|
@ -108,79 +118,78 @@ import (
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
func (m *Module) StreamGETHandler(c *gin.Context) {
|
func (m *Module) StreamGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "StreamGETHandler")
|
|
||||||
|
|
||||||
streamType := c.Query(StreamQueryKey)
|
streamType := c.Query(StreamQueryKey)
|
||||||
if streamType == "" {
|
if streamType == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("no stream type provided under query key %s", StreamQueryKey)})
|
err := fmt.Errorf("no stream type provided under query key %s", StreamQueryKey)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken := c.Query(AccessTokenQueryKey)
|
accessToken := c.Query(AccessTokenQueryKey)
|
||||||
if accessToken == "" {
|
if accessToken == "" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("no access token provided under query key %s", AccessTokenQueryKey)})
|
err := fmt.Errorf("no access token provided under query key %s", AccessTokenQueryKey)
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure a valid token has been provided and obtain the associated account
|
account, errWithCode := m.processor.AuthorizeStreamingRequest(c.Request.Context(), accessToken)
|
||||||
account, err := m.processor.AuthorizeStreamingRequest(c.Request.Context(), accessToken)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "could not authorize with given token"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare to upgrade the connection to a websocket connection
|
|
||||||
upgrader := websocket.Upgrader{
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
// we fully expect cors requests (via something like pinafore.social) so we should be lenient here
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the actual upgrade here
|
|
||||||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
||||||
if err != nil {
|
|
||||||
l.Infof("error upgrading websocket connection: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer conn.Close() // whatever happens, when we leave this function we want to close the websocket connection
|
|
||||||
|
|
||||||
// inform the processor that we have a new connection and want a s for it
|
|
||||||
s, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType)
|
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
c.JSON(errWithCode.Code(), errWithCode.Safe())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer close(s.Hangup) // closing stream.Hangup indicates that we've finished with the connection (the client has gone), so we want to do this on exiting this handler
|
|
||||||
|
|
||||||
// spawn a new ticker for pinging the connection periodically
|
stream, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType)
|
||||||
t := time.NewTicker(30 * time.Second)
|
if errWithCode != nil {
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// we want to stay in the sendloop as long as possible while the client is connected -- the only thing that should break the loop is if the client leaves or something else goes wrong
|
l := logrus.WithFields(logrus.Fields{
|
||||||
sendLoop:
|
"account": account.Username,
|
||||||
|
"path": BasePath,
|
||||||
|
"streamID": stream.ID,
|
||||||
|
"streamType": streamType,
|
||||||
|
})
|
||||||
|
|
||||||
|
wsConn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
// If the upgrade fails, then Upgrade replies to the client with an HTTP error response.
|
||||||
|
// Because websocket issues are a pretty common source of headaches, we should also log
|
||||||
|
// this at Error to make this plenty visible and help admins out a bit.
|
||||||
|
l.Errorf("error upgrading websocket connection: %s", err)
|
||||||
|
close(stream.Hangup)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// cleanup
|
||||||
|
wsConn.Close()
|
||||||
|
close(stream.Hangup)
|
||||||
|
}()
|
||||||
|
|
||||||
|
streamTicker := time.NewTicker(30 * time.Second)
|
||||||
|
|
||||||
|
// We want to stay in the loop as long as possible while the client is connected.
|
||||||
|
// The only thing that should break the loop is if the client leaves or the connection becomes unhealthy.
|
||||||
|
//
|
||||||
|
// If the loop does break, we expect the client to reattempt connection, so it's cheap to leave + try again
|
||||||
|
wsLoop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case m := <-s.Messages:
|
case m := <-stream.Messages:
|
||||||
// we've got a streaming message!!
|
|
||||||
l.Trace("received message from stream")
|
l.Trace("received message from stream")
|
||||||
if err := conn.WriteJSON(m); err != nil {
|
if err := wsConn.WriteJSON(m); err != nil {
|
||||||
l.Debugf("error writing json to websocket connection: %s", err)
|
l.Debugf("error writing json to websocket connection; breaking off: %s", err)
|
||||||
// if something is wrong we want to bail and drop the connection -- the client will create a new one
|
break wsLoop
|
||||||
break sendLoop
|
|
||||||
}
|
}
|
||||||
l.Trace("wrote message into websocket connection")
|
l.Trace("wrote message into websocket connection")
|
||||||
case <-t.C:
|
case <-streamTicker.C:
|
||||||
l.Trace("received TICK from ticker")
|
l.Trace("received TICK from ticker")
|
||||||
if err := conn.WriteMessage(websocket.PingMessage, []byte(": ping")); err != nil {
|
if err := wsConn.WriteMessage(websocket.PingMessage, []byte(": ping")); err != nil {
|
||||||
l.Debugf("error writing ping to websocket connection: %s", err)
|
l.Debugf("error writing ping to websocket connection; breaking off: %s", err)
|
||||||
// if something is wrong we want to bail and drop the connection -- the client will create a new one
|
break wsLoop
|
||||||
break sendLoop
|
|
||||||
}
|
}
|
||||||
l.Trace("wrote ping message into websocket connection")
|
l.Trace("wrote ping message into websocket connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Trace("leaving StreamGETHandler")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,13 @@
|
||||||
package timeline
|
package timeline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -105,17 +105,14 @@ import (
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
func (m *Module) HomeTimelineGETHandler(c *gin.Context) {
|
func (m *Module) HomeTimelineGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "HomeTimelineGETHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error authing: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,8 +139,8 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {
|
||||||
if limitString != "" {
|
if limitString != "" {
|
||||||
i, err := strconv.ParseInt(limitString, 10, 64)
|
i, err := strconv.ParseInt(limitString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing limit string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", LimitKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit = int(i)
|
limit = int(i)
|
||||||
|
@ -154,8 +151,8 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {
|
||||||
if localString != "" {
|
if localString != "" {
|
||||||
i, err := strconv.ParseBool(localString)
|
i, err := strconv.ParseBool(localString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing local string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", LocalKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse local query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
local = i
|
local = i
|
||||||
|
@ -163,8 +160,7 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
resp, errWithCode := m.processor.HomeTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local)
|
resp, errWithCode := m.processor.HomeTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error from processor HomeTimelineGet: %s", errWithCode)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@
|
||||||
package timeline
|
package timeline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -105,17 +105,14 @@ import (
|
||||||
// '400':
|
// '400':
|
||||||
// description: bad request
|
// description: bad request
|
||||||
func (m *Module) PublicTimelineGETHandler(c *gin.Context) {
|
func (m *Module) PublicTimelineGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "PublicTimelineGETHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error authing: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,8 +139,8 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {
|
||||||
if limitString != "" {
|
if limitString != "" {
|
||||||
i, err := strconv.ParseInt(limitString, 10, 64)
|
i, err := strconv.ParseInt(limitString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing limit string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", LimitKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit = int(i)
|
limit = int(i)
|
||||||
|
@ -154,8 +151,8 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {
|
||||||
if localString != "" {
|
if localString != "" {
|
||||||
i, err := strconv.ParseBool(localString)
|
i, err := strconv.ParseBool(localString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing local string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", LocalKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse local query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
local = i
|
local = i
|
||||||
|
@ -163,8 +160,7 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
resp, errWithCode := m.processor.PublicTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local)
|
resp, errWithCode := m.processor.PublicTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error from processor PublicTimelineGet: %s", errWithCode)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,13 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,48 +55,48 @@ import (
|
||||||
// responses:
|
// responses:
|
||||||
// '200':
|
// '200':
|
||||||
// description: Change successful
|
// description: Change successful
|
||||||
|
// '400':
|
||||||
|
// description: bad request
|
||||||
// '401':
|
// '401':
|
||||||
// description: unauthorized
|
// description: unauthorized
|
||||||
// '403':
|
// '403':
|
||||||
// description: forbidden
|
// description: forbidden
|
||||||
// '400':
|
// '406':
|
||||||
// description: bad request
|
// description: not acceptable
|
||||||
// '500':
|
// '500':
|
||||||
// description: "internal error"
|
// description: internal error
|
||||||
func (m *Module) PasswordChangePOSTHandler(c *gin.Context) {
|
func (m *Module) PasswordChangePOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithField("func", "PasswordChangePOSTHandler")
|
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, true, true, true, true)
|
authed, err := oauth.Authed(c, true, true, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error authing: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// First check this user/account is active.
|
|
||||||
if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() {
|
|
||||||
l.Debugf("couldn't auth: %s", err)
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form := &model.PasswordChangeRequest{}
|
form := &model.PasswordChangeRequest{}
|
||||||
if err := c.ShouldBind(form); err != nil || form == nil || form.NewPassword == "" || form.OldPassword == "" {
|
if err := c.ShouldBind(form); err != nil {
|
||||||
if err != nil {
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
l.Debugf("could not parse form from request: %s", err)
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "missing one or more required form values"})
|
|
||||||
|
if form.OldPassword == "" {
|
||||||
|
err := errors.New("password change request missing field old_password")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.NewPassword == "" {
|
||||||
|
err := errors.New("password change request missing field new_password")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if errWithCode := m.processor.UserChangePassword(c.Request.Context(), authed, form); errWithCode != nil {
|
if errWithCode := m.processor.UserChangePassword(c.Request.Context(), authed, form); errWithCode != nil {
|
||||||
l.Debugf("error changing user password: %s", errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordChangePOST() {
|
||||||
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
||||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", user.PasswordChangePath), nil)
|
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", user.PasswordChangePath), nil)
|
||||||
ctx.Request.Header.Set("accept", "application/json")
|
ctx.Request.Header.Set("accept", "application/json")
|
||||||
ctx.Request.Form = url.Values{
|
ctx.Request.Form = url.Values{
|
||||||
"old_password": {"password"},
|
"old_password": {"password"},
|
||||||
|
@ -83,7 +83,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordMissingOldPassword() {
|
||||||
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
||||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", user.PasswordChangePath), nil)
|
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", user.PasswordChangePath), nil)
|
||||||
ctx.Request.Header.Set("accept", "application/json")
|
ctx.Request.Header.Set("accept", "application/json")
|
||||||
ctx.Request.Form = url.Values{
|
ctx.Request.Form = url.Values{
|
||||||
"new_password": {"peepeepoopoopassword"},
|
"new_password": {"peepeepoopoopassword"},
|
||||||
|
@ -97,7 +97,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordMissingOldPassword() {
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(`{"error":"missing one or more required form values"}`, string(b))
|
suite.Equal(`{"error":"Bad Request: password change request missing field old_password"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PasswordChangeTestSuite) TestPasswordIncorrectOldPassword() {
|
func (suite *PasswordChangeTestSuite) TestPasswordIncorrectOldPassword() {
|
||||||
|
@ -110,7 +110,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordIncorrectOldPassword() {
|
||||||
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
||||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", user.PasswordChangePath), nil)
|
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", user.PasswordChangePath), nil)
|
||||||
ctx.Request.Header.Set("accept", "application/json")
|
ctx.Request.Header.Set("accept", "application/json")
|
||||||
ctx.Request.Form = url.Values{
|
ctx.Request.Form = url.Values{
|
||||||
"old_password": {"notright"},
|
"old_password": {"notright"},
|
||||||
|
@ -125,7 +125,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordIncorrectOldPassword() {
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(`{"error":"bad request: old password did not match"}`, string(b))
|
suite.Equal(`{"error":"Bad Request: old password did not match"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PasswordChangeTestSuite) TestPasswordWeakNewPassword() {
|
func (suite *PasswordChangeTestSuite) TestPasswordWeakNewPassword() {
|
||||||
|
@ -138,7 +138,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordWeakNewPassword() {
|
||||||
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
||||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", user.PasswordChangePath), nil)
|
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", user.PasswordChangePath), nil)
|
||||||
ctx.Request.Header.Set("accept", "application/json")
|
ctx.Request.Header.Set("accept", "application/json")
|
||||||
ctx.Request.Form = url.Values{
|
ctx.Request.Form = url.Values{
|
||||||
"old_password": {"password"},
|
"old_password": {"password"},
|
||||||
|
@ -153,7 +153,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordWeakNewPassword() {
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(`{"error":"bad request: password is 94% strength, try including more special characters, using uppercase letters, using numbers or using a longer password"}`, string(b))
|
suite.Equal(`{"error":"Bad Request: password is 94% strength, try including more special characters, using uppercase letters, using numbers or using a longer password"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPasswordChangeTestSuite(t *testing.T) {
|
func TestPasswordChangeTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -56,8 +56,8 @@ type UserStandardTestSuite struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *UserStandardTestSuite) SetupTest() {
|
func (suite *UserStandardTestSuite) SetupTest() {
|
||||||
testrig.InitTestLog()
|
|
||||||
testrig.InitTestConfig()
|
testrig.InitTestConfig()
|
||||||
|
testrig.InitTestLog()
|
||||||
fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
|
fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)
|
||||||
clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1)
|
clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1)
|
||||||
suite.testTokens = testrig.NewTestTokens()
|
suite.testTokens = testrig.NewTestTokens()
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: add more templated html pages here for different error types
|
||||||
|
|
||||||
|
// NotFoundHandler serves a 404 html page through the provided gin context,
|
||||||
|
// if accept is 'text/html', or just returns a json error if 'accept' is empty
|
||||||
|
// or application/json.
|
||||||
|
//
|
||||||
|
// When serving html, NotFoundHandler calls the provided InstanceGet function
|
||||||
|
// to fetch the apimodel representation of the instance, for serving in the
|
||||||
|
// 404 header and footer.
|
||||||
|
//
|
||||||
|
// If an error is returned by InstanceGet, the function will panic.
|
||||||
|
func NotFoundHandler(c *gin.Context, instanceGet func(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode), accept string) {
|
||||||
|
switch accept {
|
||||||
|
case string(TextHTML):
|
||||||
|
host := config.GetHost()
|
||||||
|
instance, err := instanceGet(c.Request.Context(), host)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusNotFound, "404.tmpl", gin.H{
|
||||||
|
"instance": instance,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": http.StatusText(http.StatusNotFound)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// genericErrorHandler is a more general version of the NotFoundHandler, which can
|
||||||
|
// be used for serving either generic error pages with some rendered help text,
|
||||||
|
// or just some error json if the caller prefers (or has no preference).
|
||||||
|
func genericErrorHandler(c *gin.Context, instanceGet func(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode), accept string, errWithCode gtserror.WithCode) {
|
||||||
|
switch accept {
|
||||||
|
case string(TextHTML):
|
||||||
|
host := config.GetHost()
|
||||||
|
instance, err := instanceGet(c.Request.Context(), host)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(errWithCode.Code(), "error.tmpl", gin.H{
|
||||||
|
"instance": instance,
|
||||||
|
"code": errWithCode.Code(),
|
||||||
|
"error": errWithCode.Safe(),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorHandler takes the provided gin context and errWithCode and tries to serve
|
||||||
|
// a helpful error to the caller. It will do content negotiation to figure out if
|
||||||
|
// the caller prefers to see an html page with the error rendered there. If not, or
|
||||||
|
// if something goes wrong during the function, it will recover and just try to serve
|
||||||
|
// an appropriate application/json content-type error.
|
||||||
|
func ErrorHandler(c *gin.Context, errWithCode gtserror.WithCode, instanceGet func(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode)) {
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
if raw := c.Request.URL.RawQuery; raw != "" {
|
||||||
|
path = path + "?" + raw
|
||||||
|
}
|
||||||
|
|
||||||
|
l := logrus.WithFields(logrus.Fields{
|
||||||
|
"path": path,
|
||||||
|
"error": errWithCode.Error(),
|
||||||
|
})
|
||||||
|
|
||||||
|
statusCode := errWithCode.Code()
|
||||||
|
|
||||||
|
if statusCode == http.StatusInternalServerError {
|
||||||
|
l.Error("Internal Server Error")
|
||||||
|
} else {
|
||||||
|
l.Debug("handling error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we panic for any reason during error handling,
|
||||||
|
// we should still try to return a basic code
|
||||||
|
defer func() {
|
||||||
|
if p := recover(); p != nil {
|
||||||
|
l.Warnf("recovered from panic: %s", p)
|
||||||
|
c.JSON(statusCode, gin.H{"error": errWithCode.Safe()})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// discover if we're allowed to serve a nice html error page,
|
||||||
|
// or if we should just use a json. Normally we would want to
|
||||||
|
// check for a returned error, but if an error occurs here we
|
||||||
|
// can just fall back to default behavior (serve json error).
|
||||||
|
accept, _ := NegotiateAccept(c, HTMLOrJSONAcceptHeaders...)
|
||||||
|
|
||||||
|
if statusCode == http.StatusNotFound {
|
||||||
|
// use our special not found handler with useful status text
|
||||||
|
NotFoundHandler(c, instanceGet, accept)
|
||||||
|
} else {
|
||||||
|
genericErrorHandler(c, instanceGet, accept, errWithCode)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,4 +16,19 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package gtserror
|
package api
|
||||||
|
|
||||||
|
// MIME represents a mime-type.
|
||||||
|
type MIME string
|
||||||
|
|
||||||
|
// MIME type
|
||||||
|
const (
|
||||||
|
AppJSON MIME = `application/json`
|
||||||
|
AppXML MIME = `application/xml`
|
||||||
|
AppActivityJSON MIME = `application/activity+json`
|
||||||
|
AppActivityLDJSON MIME = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
|
||||||
|
AppForm MIME = `application/x-www-form-urlencoded`
|
||||||
|
MultipartForm MIME = `multipart/form-data`
|
||||||
|
TextXML MIME = `text/xml`
|
||||||
|
TextHTML MIME = `text/html`
|
||||||
|
)
|
|
@ -25,33 +25,40 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Offer represents an offered mime-type.
|
|
||||||
type Offer string
|
|
||||||
|
|
||||||
const (
|
|
||||||
AppJSON Offer = `application/json` // AppJSON is the mime type for 'application/json'.
|
|
||||||
AppActivityJSON Offer = `application/activity+json` // AppActivityJSON is the mime type for 'application/activity+json'.
|
|
||||||
AppActivityLDJSON Offer = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` // AppActivityLDJSON is the mime type for 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
|
||||||
TextHTML Offer = `text/html` // TextHTML is the mime type for 'text/html'.
|
|
||||||
)
|
|
||||||
|
|
||||||
// ActivityPubAcceptHeaders represents the Accept headers mentioned here:
|
// ActivityPubAcceptHeaders represents the Accept headers mentioned here:
|
||||||
// https://www.w3.org/TR/activitypub/#retrieving-objects
|
//
|
||||||
var ActivityPubAcceptHeaders = []Offer{
|
var ActivityPubAcceptHeaders = []MIME{
|
||||||
AppActivityJSON,
|
AppActivityJSON,
|
||||||
AppActivityLDJSON,
|
AppActivityLDJSON,
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONAcceptHeaders is a slice of offers that just contains application/json types.
|
// JSONAcceptHeaders is a slice of offers that just contains application/json types.
|
||||||
var JSONAcceptHeaders = []Offer{
|
var JSONAcceptHeaders = []MIME{
|
||||||
|
AppJSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLOrJSONAcceptHeaders is a slice of offers that prefers TextHTML and will
|
||||||
|
// fall back to JSON if necessary. This is useful for error handling, since it can
|
||||||
|
// be used to serve a nice HTML page if the caller accepts that, or just JSON if not.
|
||||||
|
var HTMLOrJSONAcceptHeaders = []MIME{
|
||||||
|
TextHTML,
|
||||||
AppJSON,
|
AppJSON,
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTMLAcceptHeaders is a slice of offers that just contains text/html types.
|
// HTMLAcceptHeaders is a slice of offers that just contains text/html types.
|
||||||
var HTMLAcceptHeaders = []Offer{
|
var HTMLAcceptHeaders = []MIME{
|
||||||
TextHTML,
|
TextHTML,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTMLOrActivityPubHeaders matches text/html first, then activitypub types.
|
||||||
|
// This is useful for user URLs that a user might go to in their browser.
|
||||||
|
// https://www.w3.org/TR/activitypub/#retrieving-objects
|
||||||
|
var HTMLOrActivityPubHeaders = []MIME{
|
||||||
|
TextHTML,
|
||||||
|
AppActivityJSON,
|
||||||
|
AppActivityLDJSON,
|
||||||
|
}
|
||||||
|
|
||||||
// NegotiateAccept takes the *gin.Context from an incoming request, and a
|
// NegotiateAccept takes the *gin.Context from an incoming request, and a
|
||||||
// slice of Offers, and performs content negotiation for the given request
|
// slice of Offers, and performs content negotiation for the given request
|
||||||
// with the given content-type offers. It will return a string representation
|
// with the given content-type offers. It will return a string representation
|
||||||
|
@ -73,7 +80,7 @@ var HTMLAcceptHeaders = []Offer{
|
||||||
// often-used Accept types.
|
// often-used Accept types.
|
||||||
//
|
//
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation#server-driven_content_negotiation
|
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation#server-driven_content_negotiation
|
||||||
func NegotiateAccept(c *gin.Context, offers ...Offer) (string, error) {
|
func NegotiateAccept(c *gin.Context, offers ...MIME) (string, error) {
|
||||||
if len(offers) == 0 {
|
if len(offers) == 0 {
|
||||||
return "", errors.New("no format offered")
|
return "", errors.New("no format offered")
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NodeInfoGETHandler swagger:operation GET /nodeinfo/2.0 nodeInfoGet
|
// NodeInfoGETHandler swagger:operation GET /nodeinfo/2.0 nodeInfoGet
|
||||||
|
@ -45,27 +45,22 @@ import (
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/nodeinfo"
|
// "$ref": "#/definitions/nodeinfo"
|
||||||
func (m *Module) NodeInfoGETHandler(c *gin.Context) {
|
func (m *Module) NodeInfoGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "NodeInfoGETHandler",
|
|
||||||
"user-agent": c.Request.UserAgent(),
|
|
||||||
})
|
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ni, err := m.processor.GetNodeInfo(c.Request.Context(), c.Request)
|
ni, errWithCode := m.processor.GetNodeInfo(c.Request.Context(), c.Request)
|
||||||
|
if errWithCode != nil {
|
||||||
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(ni)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error with get node info request: %s", err)
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||||
c.JSON(err.Code(), err.Safe())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, jsonErr := json.Marshal(ni)
|
|
||||||
if jsonErr != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": jsonErr.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data(http.StatusOK, `application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"`, b)
|
c.Data(http.StatusOK, `application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"`, b)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NodeInfoWellKnownGETHandler swagger:operation GET /.well-known/nodeinfo nodeInfoWellKnownGet
|
// NodeInfoWellKnownGETHandler swagger:operation GET /.well-known/nodeinfo nodeInfoWellKnownGet
|
||||||
|
@ -45,19 +45,14 @@ import (
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/wellKnownResponse"
|
// "$ref": "#/definitions/wellKnownResponse"
|
||||||
func (m *Module) NodeInfoWellKnownGETHandler(c *gin.Context) {
|
func (m *Module) NodeInfoWellKnownGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "NodeInfoWellKnownGETHandler",
|
|
||||||
})
|
|
||||||
|
|
||||||
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
niRel, err := m.processor.GetNodeInfoRel(c.Request.Context(), c.Request)
|
niRel, errWithCode := m.processor.GetNodeInfoRel(c.Request.Context(), c.Request)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("error with get node info rel request: %s", err)
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(err.Code(), err.Safe())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,48 +20,45 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FollowersGETHandler returns a collection of URIs for followers of the target user, formatted so that other AP servers can understand it.
|
// FollowersGETHandler returns a collection of URIs for followers of the target user, formatted so that other AP servers can understand it.
|
||||||
func (m *Module) FollowersGETHandler(c *gin.Context) {
|
func (m *Module) FollowersGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
// usernames on our instance are always lowercase
|
||||||
"func": "FollowersGETHandler",
|
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||||
"url": c.Request.RequestURI,
|
|
||||||
})
|
|
||||||
|
|
||||||
requestedUsername := c.Param(UsernameKey)
|
|
||||||
if requestedUsername == "" {
|
if requestedUsername == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
err := errors.New("no username specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
format, err := api.NegotiateAccept(c, api.ActivityPubAcceptHeaders...)
|
format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
|
||||||
|
|
||||||
ctx := transferContext(c)
|
if format == string(api.TextHTML) {
|
||||||
|
// redirect to the user's profile
|
||||||
|
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||||
|
}
|
||||||
|
|
||||||
followers, errWithCode := m.processor.GetFediFollowers(ctx, requestedUsername, c.Request.URL)
|
resp, errWithCode := m.processor.GetFediFollowers(transferContext(c), requestedUsername, c.Request.URL)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Info(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, mErr := json.Marshal(followers)
|
b, err := json.Marshal(resp)
|
||||||
if mErr != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("could not marshal json: %s", mErr)
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||||
l.Error(err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,48 +20,45 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FollowingGETHandler returns a collection of URIs for accounts that the target user follows, formatted so that other AP servers can understand it.
|
// FollowingGETHandler returns a collection of URIs for accounts that the target user follows, formatted so that other AP servers can understand it.
|
||||||
func (m *Module) FollowingGETHandler(c *gin.Context) {
|
func (m *Module) FollowingGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
// usernames on our instance are always lowercase
|
||||||
"func": "FollowingGETHandler",
|
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||||
"url": c.Request.RequestURI,
|
|
||||||
})
|
|
||||||
|
|
||||||
requestedUsername := c.Param(UsernameKey)
|
|
||||||
if requestedUsername == "" {
|
if requestedUsername == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
err := errors.New("no username specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
format, err := api.NegotiateAccept(c, api.ActivityPubAcceptHeaders...)
|
format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
|
||||||
|
|
||||||
ctx := transferContext(c)
|
if format == string(api.TextHTML) {
|
||||||
|
// redirect to the user's profile
|
||||||
|
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||||
|
}
|
||||||
|
|
||||||
following, errWithCode := m.processor.GetFediFollowing(ctx, requestedUsername, c.Request.URL)
|
resp, errWithCode := m.processor.GetFediFollowing(transferContext(c), requestedUsername, c.Request.URL)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Info(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, mErr := json.Marshal(following)
|
b, err := json.Marshal(resp)
|
||||||
if mErr != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("could not marshal json: %s", mErr)
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||||
l.Error(err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,43 +19,33 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror" //nolint:typecheck
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror" //nolint:typecheck
|
||||||
)
|
)
|
||||||
|
|
||||||
// InboxPOSTHandler deals with incoming POST requests to an actor's inbox.
|
// InboxPOSTHandler deals with incoming POST requests to an actor's inbox.
|
||||||
// Eg., POST to https://example.org/users/whatever/inbox.
|
// Eg., POST to https://example.org/users/whatever/inbox.
|
||||||
func (m *Module) InboxPOSTHandler(c *gin.Context) {
|
func (m *Module) InboxPOSTHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
// usernames on our instance are always lowercase
|
||||||
"func": "InboxPOSTHandler",
|
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||||
"url": c.Request.RequestURI,
|
|
||||||
})
|
|
||||||
|
|
||||||
requestedUsername := c.Param(UsernameKey)
|
|
||||||
if requestedUsername == "" {
|
if requestedUsername == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
err := errors.New("no username specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := transferContext(c)
|
if posted, err := m.processor.InboxPost(transferContext(c), c.Writer, c.Request); err != nil {
|
||||||
|
|
||||||
posted, err := m.processor.InboxPost(ctx, c.Writer, c.Request)
|
|
||||||
if err != nil {
|
|
||||||
if withCode, ok := err.(gtserror.WithCode); ok {
|
if withCode, ok := err.(gtserror.WithCode); ok {
|
||||||
l.Debugf("InboxPOSTHandler: %s", withCode.Error())
|
api.ErrorHandler(c, withCode, m.processor.InstanceGet)
|
||||||
c.JSON(withCode.Code(), withCode.Safe())
|
} else {
|
||||||
return
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
}
|
}
|
||||||
l.Debugf("InboxPOSTHandler: error processing request: %s", err)
|
} else if !posted {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"})
|
err := errors.New("unable to process request")
|
||||||
return
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
}
|
|
||||||
|
|
||||||
if !posted {
|
|
||||||
l.Debugf("InboxPOSTHandler: request could not be handled as an AP request; headers were: %+v", c.Request.Header)
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,13 +20,15 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OutboxGETHandler swagger:operation GET /users/{username}/outbox s2sOutboxGet
|
// OutboxGETHandler swagger:operation GET /users/{username}/outbox s2sOutboxGet
|
||||||
|
@ -80,23 +82,31 @@ import (
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
func (m *Module) OutboxGETHandler(c *gin.Context) {
|
func (m *Module) OutboxGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
// usernames on our instance are always lowercase
|
||||||
"func": "OutboxGETHandler",
|
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||||
"url": c.Request.RequestURI,
|
|
||||||
})
|
|
||||||
|
|
||||||
requestedUsername := c.Param(UsernameKey)
|
|
||||||
if requestedUsername == "" {
|
if requestedUsername == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
err := errors.New("no username specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...)
|
||||||
|
if err != nil {
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if format == string(api.TextHTML) {
|
||||||
|
// redirect to the user's profile
|
||||||
|
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||||
|
}
|
||||||
|
|
||||||
var page bool
|
var page bool
|
||||||
if pageString := c.Query(PageKey); pageString != "" {
|
if pageString := c.Query(PageKey); pageString != "" {
|
||||||
i, err := strconv.ParseBool(pageString)
|
i, err := strconv.ParseBool(pageString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing page string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", PageKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse page query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
page = i
|
page = i
|
||||||
|
@ -114,27 +124,15 @@ func (m *Module) OutboxGETHandler(c *gin.Context) {
|
||||||
maxID = maxIDString
|
maxID = maxIDString
|
||||||
}
|
}
|
||||||
|
|
||||||
format, err := api.NegotiateAccept(c, api.ActivityPubAcceptHeaders...)
|
resp, errWithCode := m.processor.GetFediOutbox(transferContext(c), requestedUsername, page, maxID, minID, c.Request.URL)
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.Tracef("negotiated format: %s", format)
|
|
||||||
|
|
||||||
ctx := transferContext(c)
|
|
||||||
|
|
||||||
outbox, errWithCode := m.processor.GetFediOutbox(ctx, requestedUsername, page, maxID, minID, c.Request.URL)
|
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Info(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, mErr := json.Marshal(outbox)
|
b, err := json.Marshal(resp)
|
||||||
if mErr != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("could not marshal json: %s", mErr)
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||||
l.Error(err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,13 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublicKeyGETHandler should be served at eg https://example.org/users/:username/main-key.
|
// PublicKeyGETHandler should be served at eg https://example.org/users/:username/main-key.
|
||||||
|
@ -34,38 +35,34 @@ import (
|
||||||
// in the form of a vocab.ActivityStreamsPerson. The account will only contain the id,
|
// in the form of a vocab.ActivityStreamsPerson. The account will only contain the id,
|
||||||
// public key, username, and type of the account.
|
// public key, username, and type of the account.
|
||||||
func (m *Module) PublicKeyGETHandler(c *gin.Context) {
|
func (m *Module) PublicKeyGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
// usernames on our instance are always lowercase
|
||||||
"func": "PublicKeyGETHandler",
|
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||||
"url": c.Request.RequestURI,
|
|
||||||
})
|
|
||||||
|
|
||||||
requestedUsername := c.Param(UsernameKey)
|
|
||||||
if requestedUsername == "" {
|
if requestedUsername == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
err := errors.New("no username specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
format, err := api.NegotiateAccept(c, api.ActivityPubAcceptHeaders...)
|
format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
|
||||||
|
|
||||||
ctx := transferContext(c)
|
if format == string(api.TextHTML) {
|
||||||
|
// redirect to the user's profile
|
||||||
|
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||||
|
}
|
||||||
|
|
||||||
user, errWithCode := m.processor.GetFediUser(ctx, requestedUsername, c.Request.URL)
|
resp, errWithCode := m.processor.GetFediUser(transferContext(c), requestedUsername, c.Request.URL)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Info(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, mErr := json.Marshal(user)
|
b, err := json.Marshal(resp)
|
||||||
if mErr != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("could not marshal json: %s", mErr)
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||||
l.Error(err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,15 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusRepliesGETHandler swagger:operation GET /users/{username}/statuses/{status}/replies s2sRepliesGet
|
// StatusRepliesGETHandler swagger:operation GET /users/{username}/statuses/{status}/replies s2sRepliesGet
|
||||||
|
@ -86,29 +88,39 @@ import (
|
||||||
// '404':
|
// '404':
|
||||||
// description: not found
|
// description: not found
|
||||||
func (m *Module) StatusRepliesGETHandler(c *gin.Context) {
|
func (m *Module) StatusRepliesGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
// usernames on our instance are always lowercase
|
||||||
"func": "StatusRepliesGETHandler",
|
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||||
"url": c.Request.RequestURI,
|
|
||||||
})
|
|
||||||
|
|
||||||
requestedUsername := c.Param(UsernameKey)
|
|
||||||
if requestedUsername == "" {
|
if requestedUsername == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
err := errors.New("no username specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedStatusID := c.Param(StatusIDKey)
|
// status IDs on our instance are always uppercase
|
||||||
|
requestedStatusID := strings.ToUpper(c.Param(StatusIDKey))
|
||||||
if requestedStatusID == "" {
|
if requestedStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id specified in request"})
|
err := errors.New("no status id specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...)
|
||||||
|
if err != nil {
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if format == string(api.TextHTML) {
|
||||||
|
// redirect to the status
|
||||||
|
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername+"/statuses/"+requestedStatusID)
|
||||||
|
}
|
||||||
|
|
||||||
var page bool
|
var page bool
|
||||||
if pageString := c.Query(PageKey); pageString != "" {
|
if pageString := c.Query(PageKey); pageString != "" {
|
||||||
i, err := strconv.ParseBool(pageString)
|
i, err := strconv.ParseBool(pageString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing page string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", PageKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse page query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
page = i
|
page = i
|
||||||
|
@ -119,8 +131,8 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {
|
||||||
if onlyOtherAccountsString != "" {
|
if onlyOtherAccountsString != "" {
|
||||||
i, err := strconv.ParseBool(onlyOtherAccountsString)
|
i, err := strconv.ParseBool(onlyOtherAccountsString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error parsing only_other_accounts string: %s", err)
|
err := fmt.Errorf("error parsing %s: %s", OnlyOtherAccountsKey, err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse only_other_accounts query param"})
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onlyOtherAccounts = i
|
onlyOtherAccounts = i
|
||||||
|
@ -132,27 +144,15 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {
|
||||||
minID = minIDString
|
minID = minIDString
|
||||||
}
|
}
|
||||||
|
|
||||||
format, err := api.NegotiateAccept(c, api.ActivityPubAcceptHeaders...)
|
resp, errWithCode := m.processor.GetFediStatusReplies(transferContext(c), requestedUsername, requestedStatusID, page, onlyOtherAccounts, minID, c.Request.URL)
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.Tracef("negotiated format: %s", format)
|
|
||||||
|
|
||||||
ctx := transferContext(c)
|
|
||||||
|
|
||||||
replies, errWithCode := m.processor.GetFediStatusReplies(ctx, requestedUsername, requestedStatusID, page, onlyOtherAccounts, minID, c.Request.URL)
|
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Info(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, mErr := json.Marshal(replies)
|
b, err := json.Marshal(resp)
|
||||||
if mErr != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("could not marshal json: %s", mErr)
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||||
l.Error(err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,57 +20,53 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusGETHandler serves the target status as an activitystreams NOTE so that other AP servers can parse it.
|
// StatusGETHandler serves the target status as an activitystreams NOTE so that other AP servers can parse it.
|
||||||
func (m *Module) StatusGETHandler(c *gin.Context) {
|
func (m *Module) StatusGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "StatusGETHandler",
|
|
||||||
"url": c.Request.RequestURI,
|
|
||||||
})
|
|
||||||
|
|
||||||
// usernames on our instance are always lowercase
|
// usernames on our instance are always lowercase
|
||||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||||
if requestedUsername == "" {
|
if requestedUsername == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
err := errors.New("no username specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// status IDs on our instance are always uppercase
|
// status IDs on our instance are always uppercase
|
||||||
requestedStatusID := strings.ToUpper(c.Param(StatusIDKey))
|
requestedStatusID := strings.ToUpper(c.Param(StatusIDKey))
|
||||||
if requestedStatusID == "" {
|
if requestedStatusID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id specified in request"})
|
err := errors.New("no status id specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
format, err := api.NegotiateAccept(c, api.ActivityPubAcceptHeaders...)
|
format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
|
||||||
|
|
||||||
ctx := transferContext(c)
|
if format == string(api.TextHTML) {
|
||||||
|
// redirect to the status
|
||||||
|
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername+"/statuses/"+requestedStatusID)
|
||||||
|
}
|
||||||
|
|
||||||
status, errWithCode := m.processor.GetFediStatus(ctx, requestedUsername, requestedStatusID, c.Request.URL)
|
resp, errWithCode := m.processor.GetFediStatus(transferContext(c), requestedUsername, requestedStatusID, c.Request.URL)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Info(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, mErr := json.Marshal(status)
|
b, err := json.Marshal(resp)
|
||||||
if mErr != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("could not marshal json: %s", mErr)
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||||
l.Error(err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,13 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsersGETHandler should be served at https://example.org/users/:username.
|
// UsersGETHandler should be served at https://example.org/users/:username.
|
||||||
|
@ -38,38 +39,34 @@ import (
|
||||||
// And of course, the request should be refused if the account or server making the
|
// And of course, the request should be refused if the account or server making the
|
||||||
// request is blocked.
|
// request is blocked.
|
||||||
func (m *Module) UsersGETHandler(c *gin.Context) {
|
func (m *Module) UsersGETHandler(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
// usernames on our instance are always lowercase
|
||||||
"func": "UsersGETHandler",
|
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||||
"url": c.Request.RequestURI,
|
|
||||||
})
|
|
||||||
|
|
||||||
requestedUsername := c.Param(UsernameKey)
|
|
||||||
if requestedUsername == "" {
|
if requestedUsername == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
err := errors.New("no username specified in request")
|
||||||
|
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
format, err := api.NegotiateAccept(c, api.ActivityPubAcceptHeaders...)
|
format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
|
api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
|
||||||
|
|
||||||
ctx := transferContext(c)
|
if format == string(api.TextHTML) {
|
||||||
|
// redirect to the user's profile
|
||||||
|
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||||
|
}
|
||||||
|
|
||||||
user, errWithCode := m.processor.GetFediUser(ctx, requestedUsername, c.Request.URL) // GetFediUser handles auth as well
|
resp, errWithCode := m.processor.GetFediUser(transferContext(c), requestedUsername, c.Request.URL)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
l.Info(errWithCode.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, mErr := json.Marshal(user)
|
b, err := json.Marshal(resp)
|
||||||
if mErr != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("could not marshal json: %s", mErr)
|
api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||||
l.Error(err)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -105,10 +106,9 @@ func (m *Module) WebfingerGETRequest(c *gin.Context) {
|
||||||
ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeyVerifier, verifier)
|
ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeyVerifier, verifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := m.processor.GetWebfingerAccount(ctx, username)
|
resp, errWithCode := m.processor.GetWebfingerAccount(ctx, username)
|
||||||
if err != nil {
|
if errWithCode != nil {
|
||||||
l.Debugf("aborting request with an error: %s", err.Error())
|
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,21 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package security
|
package security
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
|
@ -19,21 +19,17 @@
|
||||||
package security
|
package security
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserAgentBlock blocks requests with undesired, empty, or invalid user-agent strings.
|
// UserAgentBlock aborts requests with empty user agent strings.
|
||||||
func (m *Module) UserAgentBlock(c *gin.Context) {
|
func (m *Module) UserAgentBlock(c *gin.Context) {
|
||||||
l := logrus.WithFields(logrus.Fields{
|
|
||||||
"func": "UserAgentBlock",
|
|
||||||
})
|
|
||||||
|
|
||||||
if ua := c.Request.UserAgent(); ua == "" {
|
if ua := c.Request.UserAgent(); ua == "" {
|
||||||
l.Debug("aborting request because there's no user-agent set")
|
code := http.StatusTeapot
|
||||||
c.AbortWithStatus(http.StatusTeapot)
|
err := errors.New(http.StatusText(code) + ": no user-agent sent with request")
|
||||||
return
|
c.AbortWithStatusJSON(code, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
vi := ctx.Value(ap.ContextRequestingPublicKeyVerifier)
|
vi := ctx.Value(ap.ContextRequestingPublicKeyVerifier)
|
||||||
if vi == nil {
|
if vi == nil {
|
||||||
err := errors.New("http request wasn't signed or http signature was invalid")
|
err := errors.New("http request wasn't signed or http signature was invalid")
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(err, err.Error())
|
errWithCode := gtserror.NewErrorUnauthorized(err, err.Error())
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
verifier, ok := vi.(httpsig.Verifier)
|
verifier, ok := vi.(httpsig.Verifier)
|
||||||
if !ok {
|
if !ok {
|
||||||
err := errors.New("http request wasn't signed or http signature was invalid")
|
err := errors.New("http request wasn't signed or http signature was invalid")
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(err, err.Error())
|
errWithCode := gtserror.NewErrorUnauthorized(err, err.Error())
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -143,7 +143,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
si := ctx.Value(ap.ContextRequestingPublicKeySignature)
|
si := ctx.Value(ap.ContextRequestingPublicKeySignature)
|
||||||
if si == nil {
|
if si == nil {
|
||||||
err := errors.New("http request wasn't signed or http signature was invalid")
|
err := errors.New("http request wasn't signed or http signature was invalid")
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(err, err.Error())
|
errWithCode := gtserror.NewErrorUnauthorized(err, err.Error())
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
signature, ok := si.(string)
|
signature, ok := si.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
err := errors.New("http request wasn't signed or http signature was invalid")
|
err := errors.New("http request wasn't signed or http signature was invalid")
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(err, err.Error())
|
errWithCode := gtserror.NewErrorUnauthorized(err, err.Error())
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
// The actual http call to the remote server is made right here in the Dereference function.
|
// The actual http call to the remote server is made right here in the Dereference function.
|
||||||
b, err := transport.Dereference(ctx, requestingPublicKeyID)
|
b, err := transport.Dereference(ctx, requestingPublicKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(fmt.Errorf("error dereferencing public key %s: %s", requestingPublicKeyID, err))
|
errWithCode := gtserror.NewErrorUnauthorized(fmt.Errorf("error dereferencing public key %s: %s", requestingPublicKeyID, err))
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
// if the key isn't in the response, we can't authenticate the request
|
// if the key isn't in the response, we can't authenticate the request
|
||||||
requestingPublicKey, err := getPublicKeyFromResponse(ctx, b, requestingPublicKeyID)
|
requestingPublicKey, err := getPublicKeyFromResponse(ctx, b, requestingPublicKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(fmt.Errorf("error parsing public key %s: %s", requestingPublicKeyID, err))
|
errWithCode := gtserror.NewErrorUnauthorized(fmt.Errorf("error parsing public key %s: %s", requestingPublicKeyID, err))
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
// we should be able to get the actual key embedded in the vocab.W3IDSecurityV1PublicKey
|
// we should be able to get the actual key embedded in the vocab.W3IDSecurityV1PublicKey
|
||||||
pkPemProp := requestingPublicKey.GetW3IDSecurityV1PublicKeyPem()
|
pkPemProp := requestingPublicKey.GetW3IDSecurityV1PublicKeyPem()
|
||||||
if pkPemProp == nil || !pkPemProp.IsXMLSchemaString() {
|
if pkPemProp == nil || !pkPemProp.IsXMLSchemaString() {
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(errors.New("publicKeyPem property is not provided or it is not embedded as a value"))
|
errWithCode := gtserror.NewErrorUnauthorized(errors.New("publicKeyPem property is not provided or it is not embedded as a value"))
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -234,14 +234,14 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
pubKeyPem := pkPemProp.Get()
|
pubKeyPem := pkPemProp.Get()
|
||||||
block, _ := pem.Decode([]byte(pubKeyPem))
|
block, _ := pem.Decode([]byte(pubKeyPem))
|
||||||
if block == nil || block.Type != "PUBLIC KEY" {
|
if block == nil || block.Type != "PUBLIC KEY" {
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type"))
|
errWithCode := gtserror.NewErrorUnauthorized(errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type"))
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKey, err = x509.ParsePKIXPublicKey(block.Bytes)
|
publicKey, err = x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(fmt.Errorf("could not parse public key %s from block bytes: %s", requestingPublicKeyID, err))
|
errWithCode := gtserror.NewErrorUnauthorized(fmt.Errorf("could not parse public key %s from block bytes: %s", requestingPublicKeyID, err))
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -249,7 +249,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
// all good! we just need the URI of the key owner to return
|
// all good! we just need the URI of the key owner to return
|
||||||
pkOwnerProp := requestingPublicKey.GetW3IDSecurityV1Owner()
|
pkOwnerProp := requestingPublicKey.GetW3IDSecurityV1Owner()
|
||||||
if pkOwnerProp == nil || !pkOwnerProp.IsIRI() {
|
if pkOwnerProp == nil || !pkOwnerProp.IsIRI() {
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(errors.New("publicKeyOwner property is not provided or it is not embedded as a value"))
|
errWithCode := gtserror.NewErrorUnauthorized(errors.New("publicKeyOwner property is not provided or it is not embedded as a value"))
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
@ -280,7 +280,7 @@ func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
||||||
l.Tracef("authentication for %s NOT PASSED with algorithm %s: %s", pkOwnerURI, algo, err)
|
l.Tracef("authentication for %s NOT PASSED with algorithm %s: %s", pkOwnerURI, algo, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
errWithCode := gtserror.NewErrorNotAuthorized(fmt.Errorf("authentication not passed for public key owner %s; signature value was '%s'", pkOwnerURI, signature))
|
errWithCode := gtserror.NewErrorUnauthorized(fmt.Errorf("authentication not passed for public key owner %s; signature value was '%s'", pkOwnerURI, signature))
|
||||||
l.Debug(errWithCode)
|
l.Debug(errWithCode)
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ func (e withCode) Code() int {
|
||||||
|
|
||||||
// NewErrorBadRequest returns an ErrorWithCode 400 with the given original error and optional help text.
|
// NewErrorBadRequest returns an ErrorWithCode 400 with the given original error and optional help text.
|
||||||
func NewErrorBadRequest(original error, helpText ...string) WithCode {
|
func NewErrorBadRequest(original error, helpText ...string) WithCode {
|
||||||
safe := "bad request"
|
safe := http.StatusText(http.StatusBadRequest)
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
|
@ -71,9 +71,9 @@ func NewErrorBadRequest(original error, helpText ...string) WithCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewErrorNotAuthorized returns an ErrorWithCode 401 with the given original error and optional help text.
|
// NewErrorUnauthorized returns an ErrorWithCode 401 with the given original error and optional help text.
|
||||||
func NewErrorNotAuthorized(original error, helpText ...string) WithCode {
|
func NewErrorUnauthorized(original error, helpText ...string) WithCode {
|
||||||
safe := "not authorized"
|
safe := http.StatusText(http.StatusUnauthorized)
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ func NewErrorNotAuthorized(original error, helpText ...string) WithCode {
|
||||||
|
|
||||||
// NewErrorForbidden returns an ErrorWithCode 403 with the given original error and optional help text.
|
// NewErrorForbidden returns an ErrorWithCode 403 with the given original error and optional help text.
|
||||||
func NewErrorForbidden(original error, helpText ...string) WithCode {
|
func NewErrorForbidden(original error, helpText ...string) WithCode {
|
||||||
safe := "forbidden"
|
safe := http.StatusText(http.StatusForbidden)
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ func NewErrorForbidden(original error, helpText ...string) WithCode {
|
||||||
|
|
||||||
// NewErrorNotFound returns an ErrorWithCode 404 with the given original error and optional help text.
|
// NewErrorNotFound returns an ErrorWithCode 404 with the given original error and optional help text.
|
||||||
func NewErrorNotFound(original error, helpText ...string) WithCode {
|
func NewErrorNotFound(original error, helpText ...string) WithCode {
|
||||||
safe := "404 not found"
|
safe := http.StatusText(http.StatusNotFound)
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ func NewErrorNotFound(original error, helpText ...string) WithCode {
|
||||||
|
|
||||||
// NewErrorInternalError returns an ErrorWithCode 500 with the given original error and optional help text.
|
// NewErrorInternalError returns an ErrorWithCode 500 with the given original error and optional help text.
|
||||||
func NewErrorInternalError(original error, helpText ...string) WithCode {
|
func NewErrorInternalError(original error, helpText ...string) WithCode {
|
||||||
safe := "internal server error"
|
safe := http.StatusText(http.StatusInternalServerError)
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ func NewErrorInternalError(original error, helpText ...string) WithCode {
|
||||||
|
|
||||||
// NewErrorConflict returns an ErrorWithCode 409 with the given original error and optional help text.
|
// NewErrorConflict returns an ErrorWithCode 409 with the given original error and optional help text.
|
||||||
func NewErrorConflict(original error, helpText ...string) WithCode {
|
func NewErrorConflict(original error, helpText ...string) WithCode {
|
||||||
safe := "conflict"
|
safe := http.StatusText(http.StatusConflict)
|
||||||
if helpText != nil {
|
if helpText != nil {
|
||||||
safe = safe + ": " + strings.Join(helpText, ": ")
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
}
|
}
|
||||||
|
@ -135,3 +135,29 @@ func NewErrorConflict(original error, helpText ...string) WithCode {
|
||||||
code: http.StatusConflict,
|
code: http.StatusConflict,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewErrorNotAcceptable returns an ErrorWithCode 406 with the given original error and optional help text.
|
||||||
|
func NewErrorNotAcceptable(original error, helpText ...string) WithCode {
|
||||||
|
safe := http.StatusText(http.StatusNotAcceptable)
|
||||||
|
if helpText != nil {
|
||||||
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
|
}
|
||||||
|
return withCode{
|
||||||
|
original: original,
|
||||||
|
safe: errors.New(safe),
|
||||||
|
code: http.StatusNotAcceptable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorUnprocessableEntity returns an ErrorWithCode 422 with the given original error and optional help text.
|
||||||
|
func NewErrorUnprocessableEntity(original error, helpText ...string) WithCode {
|
||||||
|
safe := http.StatusText(http.StatusUnprocessableEntity)
|
||||||
|
if helpText != nil {
|
||||||
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
|
}
|
||||||
|
return withCode{
|
||||||
|
original: original,
|
||||||
|
safe: errors.New(safe),
|
||||||
|
code: http.StatusUnprocessableEntity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -24,24 +24,28 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i *idp) HandleCallback(ctx context.Context, code string) (*Claims, error) {
|
func (i *idp) HandleCallback(ctx context.Context, code string) (*Claims, gtserror.WithCode) {
|
||||||
l := logrus.WithField("func", "HandleCallback")
|
l := logrus.WithField("func", "HandleCallback")
|
||||||
if code == "" {
|
if code == "" {
|
||||||
return nil, errors.New("code was empty string")
|
err := errors.New("code was empty string")
|
||||||
|
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Debug("exchanging code for oauth2token")
|
l.Debug("exchanging code for oauth2token")
|
||||||
oauth2Token, err := i.oauth2Config.Exchange(ctx, code)
|
oauth2Token, err := i.oauth2Config.Exchange(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error exchanging code for oauth2token: %s", err)
|
err := fmt.Errorf("error exchanging code for oauth2token: %s", err)
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Debug("extracting id_token")
|
l.Debug("extracting id_token")
|
||||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("no id_token in oauth2token")
|
err := errors.New("no id_token in oauth2token")
|
||||||
|
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||||
}
|
}
|
||||||
l.Debugf("raw id token: %s", rawIDToken)
|
l.Debugf("raw id token: %s", rawIDToken)
|
||||||
|
|
||||||
|
@ -50,13 +54,15 @@ func (i *idp) HandleCallback(ctx context.Context, code string) (*Claims, error)
|
||||||
idTokenVerifier := i.provider.Verifier(i.oidcConf)
|
idTokenVerifier := i.provider.Verifier(i.oidcConf)
|
||||||
idToken, err := idTokenVerifier.Verify(ctx, rawIDToken)
|
idToken, err := idTokenVerifier.Verify(ctx, rawIDToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not verify id token: %s", err)
|
err = fmt.Errorf("could not verify id token: %s", err)
|
||||||
|
return nil, gtserror.NewErrorUnauthorized(err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Debug("extracting claims from id_token")
|
l.Debug("extracting claims from id_token")
|
||||||
claims := &Claims{}
|
claims := &Claims{}
|
||||||
if err := idToken.Claims(claims); err != nil {
|
if err := idToken.Claims(claims); err != nil {
|
||||||
return nil, fmt.Errorf("could not parse claims from idToken: %s", err)
|
err := fmt.Errorf("could not parse claims from idToken: %s", err)
|
||||||
|
return nil, gtserror.NewErrorInternalError(err, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return claims, nil
|
return claims, nil
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ type IDP interface {
|
||||||
// with a set of claims.
|
// with a set of claims.
|
||||||
//
|
//
|
||||||
// Note that this function *does not* verify state. That should be handled by the caller *before* this function is called.
|
// Note that this function *does not* verify state. That should be handled by the caller *before* this function is called.
|
||||||
HandleCallback(ctx context.Context, code string) (*Claims, error)
|
HandleCallback(ctx context.Context, code string) (*Claims, gtserror.WithCode)
|
||||||
// AuthCodeURL returns the proper redirect URL for this IDP, for redirecting requesters to the correct OIDC endpoint.
|
// AuthCodeURL returns the proper redirect URL for this IDP, for redirecting requesters to the correct OIDC endpoint.
|
||||||
AuthCodeURL(state string) string
|
AuthCodeURL(state string) string
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *processor) AccountCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.AccountCreateRequest) (*apimodel.Token, error) {
|
func (p *processor) AccountCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.AccountCreateRequest) (*apimodel.Token, gtserror.WithCode) {
|
||||||
return p.accountProcessor.Create(ctx, authed.Token, authed.Application, form)
|
return p.accountProcessor.Create(ctx, authed.Token, authed.Application, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ func (p *processor) AccountGetLocalByUsername(ctx context.Context, authed *oauth
|
||||||
return p.accountProcessor.GetLocalByUsername(ctx, authed.Account, username)
|
return p.accountProcessor.GetLocalByUsername(ctx, authed.Account, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) {
|
func (p *processor) AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) {
|
||||||
return p.accountProcessor.Update(ctx, authed.Account, form)
|
return p.accountProcessor.Update(ctx, authed.Account, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ import (
|
||||||
// Processor wraps a bunch of functions for processing account actions.
|
// Processor wraps a bunch of functions for processing account actions.
|
||||||
type Processor interface {
|
type Processor interface {
|
||||||
// Create processes the given form for creating a new account, returning an oauth token for that account if successful.
|
// Create processes the given form for creating a new account, returning an oauth token for that account if successful.
|
||||||
Create(ctx context.Context, applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, error)
|
Create(ctx context.Context, applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, gtserror.WithCode)
|
||||||
// Delete deletes an account, and all of that account's statuses, media, follows, notifications, etc etc etc.
|
// Delete deletes an account, and all of that account's statuses, media, follows, notifications, etc etc etc.
|
||||||
// The origin passed here should be either the ID of the account doing the delete (can be itself), or the ID of a domain block.
|
// The origin passed here should be either the ID of the account doing the delete (can be itself), or the ID of a domain block.
|
||||||
Delete(ctx context.Context, account *gtsmodel.Account, origin string) gtserror.WithCode
|
Delete(ctx context.Context, account *gtsmodel.Account, origin string) gtserror.WithCode
|
||||||
|
@ -52,7 +52,7 @@ type Processor interface {
|
||||||
// GetLocalByUsername processes the given request for account information targeting a local account by username.
|
// GetLocalByUsername processes the given request for account information targeting a local account by username.
|
||||||
GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode)
|
GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode)
|
||||||
// Update processes the update of an account with the given form
|
// Update processes the update of an account with the given form
|
||||||
Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error)
|
Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode)
|
||||||
// StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
|
// StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
|
||||||
// the account given in authed.
|
// the account given in authed.
|
||||||
StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) (*apimodel.TimelineResponse, gtserror.WithCode)
|
StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) (*apimodel.TimelineResponse, gtserror.WithCode)
|
||||||
|
|
|
@ -27,29 +27,30 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/text"
|
"github.com/superseriousbusiness/gotosocial/internal/text"
|
||||||
"github.com/superseriousbusiness/oauth2/v4"
|
"github.com/superseriousbusiness/oauth2/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *processor) Create(ctx context.Context, applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, error) {
|
func (p *processor) Create(ctx context.Context, applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, gtserror.WithCode) {
|
||||||
l := logrus.WithField("func", "accountCreate")
|
l := logrus.WithField("func", "accountCreate")
|
||||||
|
|
||||||
emailAvailable, err := p.db.IsEmailAvailable(ctx, form.Email)
|
emailAvailable, err := p.db.IsEmailAvailable(ctx, form.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
if !emailAvailable {
|
if !emailAvailable {
|
||||||
return nil, fmt.Errorf("email address %s in use", form.Email)
|
return nil, gtserror.NewErrorConflict(fmt.Errorf("email address %s is not available", form.Email))
|
||||||
}
|
}
|
||||||
|
|
||||||
usernameAvailable, err := p.db.IsUsernameAvailable(ctx, form.Username)
|
usernameAvailable, err := p.db.IsUsernameAvailable(ctx, form.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
if !usernameAvailable {
|
if !usernameAvailable {
|
||||||
return nil, fmt.Errorf("username %s in use", form.Username)
|
return nil, gtserror.NewErrorConflict(fmt.Errorf("username %s in use", form.Username))
|
||||||
}
|
}
|
||||||
|
|
||||||
reasonRequired := config.GetAccountsReasonRequired()
|
reasonRequired := config.GetAccountsReasonRequired()
|
||||||
|
@ -64,19 +65,19 @@ func (p *processor) Create(ctx context.Context, applicationToken oauth2.TokenInf
|
||||||
l.Trace("creating new username and account")
|
l.Trace("creating new username and account")
|
||||||
user, err := p.db.NewSignup(ctx, form.Username, text.SanitizePlaintext(reason), approvalRequired, form.Email, form.Password, form.IP, form.Locale, application.ID, false, false)
|
user, err := p.db.NewSignup(ctx, form.Username, text.SanitizePlaintext(reason), approvalRequired, form.Email, form.Password, form.IP, form.Locale, application.ID, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating new signup in the database: %s", err)
|
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error creating new signup in the database: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Tracef("generating a token for user %s with account %s and application %s", user.ID, user.AccountID, application.ID)
|
l.Tracef("generating a token for user %s with account %s and application %s", user.ID, user.AccountID, application.ID)
|
||||||
accessToken, err := p.oauthServer.GenerateUserAccessToken(ctx, applicationToken, application.ClientSecret, user.ID)
|
accessToken, err := p.oauthServer.GenerateUserAccessToken(ctx, applicationToken, application.ClientSecret, user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating new access token for user %s: %s", user.ID, err)
|
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error creating new access token for user %s: %s", user.ID, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Account == nil {
|
if user.Account == nil {
|
||||||
a, err := p.db.GetAccountByID(ctx, user.AccountID)
|
a, err := p.db.GetAccountByID(ctx, user.AccountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting new account from the database: %s", err)
|
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting new account from the database: %s", err))
|
||||||
}
|
}
|
||||||
user.Account = a
|
user.Account = a
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,5 +94,6 @@ func (p *processor) getAccountFor(ctx context.Context, requestingAccount *gtsmod
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting account: %s", err))
|
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting account: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiAccount, nil
|
return apiAccount, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||||
|
@ -37,7 +38,7 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) {
|
func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode) {
|
||||||
l := logrus.WithField("func", "AccountUpdate")
|
l := logrus.WithField("func", "AccountUpdate")
|
||||||
|
|
||||||
if form.Discoverable != nil {
|
if form.Discoverable != nil {
|
||||||
|
@ -50,14 +51,14 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
|
|
||||||
if form.DisplayName != nil {
|
if form.DisplayName != nil {
|
||||||
if err := validate.DisplayName(*form.DisplayName); err != nil {
|
if err := validate.DisplayName(*form.DisplayName); err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
account.DisplayName = text.SanitizePlaintext(*form.DisplayName)
|
account.DisplayName = text.SanitizePlaintext(*form.DisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Note != nil {
|
if form.Note != nil {
|
||||||
if err := validate.Note(*form.Note); err != nil {
|
if err := validate.Note(*form.Note); err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the raw note before processing
|
// Set the raw note before processing
|
||||||
|
@ -66,7 +67,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
// Process note to generate a valid HTML representation
|
// Process note to generate a valid HTML representation
|
||||||
note, err := p.processNote(ctx, *form.Note, account.ID)
|
note, err := p.processNote(ctx, *form.Note, account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set updated HTML-ified note
|
// Set updated HTML-ified note
|
||||||
|
@ -76,7 +77,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
if form.Avatar != nil && form.Avatar.Size != 0 {
|
if form.Avatar != nil && form.Avatar.Size != 0 {
|
||||||
avatarInfo, err := p.UpdateAvatar(ctx, form.Avatar, account.ID)
|
avatarInfo, err := p.UpdateAvatar(ctx, form.Avatar, account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
account.AvatarMediaAttachmentID = avatarInfo.ID
|
account.AvatarMediaAttachmentID = avatarInfo.ID
|
||||||
account.AvatarMediaAttachment = avatarInfo
|
account.AvatarMediaAttachment = avatarInfo
|
||||||
|
@ -86,7 +87,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
if form.Header != nil && form.Header.Size != 0 {
|
if form.Header != nil && form.Header.Size != 0 {
|
||||||
headerInfo, err := p.UpdateHeader(ctx, form.Header, account.ID)
|
headerInfo, err := p.UpdateHeader(ctx, form.Header, account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
account.HeaderMediaAttachmentID = headerInfo.ID
|
account.HeaderMediaAttachmentID = headerInfo.ID
|
||||||
account.HeaderMediaAttachment = headerInfo
|
account.HeaderMediaAttachment = headerInfo
|
||||||
|
@ -100,7 +101,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
if form.Source != nil {
|
if form.Source != nil {
|
||||||
if form.Source.Language != nil {
|
if form.Source.Language != nil {
|
||||||
if err := validate.Language(*form.Source.Language); err != nil {
|
if err := validate.Language(*form.Source.Language); err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
account.Language = *form.Source.Language
|
account.Language = *form.Source.Language
|
||||||
}
|
}
|
||||||
|
@ -111,7 +112,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
|
|
||||||
if form.Source.Privacy != nil {
|
if form.Source.Privacy != nil {
|
||||||
if err := validate.Privacy(*form.Source.Privacy); err != nil {
|
if err := validate.Privacy(*form.Source.Privacy); err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorBadRequest(err)
|
||||||
}
|
}
|
||||||
privacy := p.tc.APIVisToVis(apimodel.Visibility(*form.Source.Privacy))
|
privacy := p.tc.APIVisToVis(apimodel.Visibility(*form.Source.Privacy))
|
||||||
account.Privacy = privacy
|
account.Privacy = privacy
|
||||||
|
@ -120,7 +121,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
|
|
||||||
updatedAccount, err := p.db.UpdateAccount(ctx, account)
|
updatedAccount, err := p.db.UpdateAccount(ctx, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not update account %s: %s", account.ID, err)
|
return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not update account %s: %s", account.ID, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
p.clientWorker.Queue(messages.FromClientAPI{
|
p.clientWorker.Queue(messages.FromClientAPI{
|
||||||
|
@ -132,7 +133,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
||||||
|
|
||||||
acctSensitive, err := p.tc.AccountToAPIAccountSensitive(ctx, updatedAccount)
|
acctSensitive, err := p.tc.AccountToAPIAccountSensitive(ctx, updatedAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not convert account into apisensitive account: %s", err)
|
return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not convert account into apisensitive account: %s", err))
|
||||||
}
|
}
|
||||||
return acctSensitive, nil
|
return acctSensitive, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,8 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateSimple() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// should get no error from the update function, and an api model account returned
|
// should get no error from the update function, and an api model account returned
|
||||||
apiAccount, err := suite.accountProcessor.Update(context.Background(), testAccount, form)
|
apiAccount, errWithCode := suite.accountProcessor.Update(context.Background(), testAccount, form)
|
||||||
suite.NoError(err)
|
suite.NoError(errWithCode)
|
||||||
suite.NotNil(apiAccount)
|
suite.NotNil(apiAccount)
|
||||||
|
|
||||||
// fields on the profile should be updated
|
// fields on the profile should be updated
|
||||||
|
@ -88,8 +88,8 @@ go check out @1happyturtle, they have a cool account!
|
||||||
}
|
}
|
||||||
|
|
||||||
// should get no error from the update function, and an api model account returned
|
// should get no error from the update function, and an api model account returned
|
||||||
apiAccount, err := suite.accountProcessor.Update(context.Background(), testAccount, form)
|
apiAccount, errWithCode := suite.accountProcessor.Update(context.Background(), testAccount, form)
|
||||||
suite.NoError(err)
|
suite.NoError(errWithCode)
|
||||||
suite.NotNil(apiAccount)
|
suite.NotNil(apiAccount)
|
||||||
|
|
||||||
// fields on the profile should be updated
|
// fields on the profile should be updated
|
||||||
|
|
|
@ -34,7 +34,7 @@ import (
|
||||||
|
|
||||||
func (p *processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, gtserror.WithCode) {
|
func (p *processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, gtserror.WithCode) {
|
||||||
if !user.Admin {
|
if !user.Admin {
|
||||||
return nil, gtserror.NewErrorNotAuthorized(fmt.Errorf("user %s not an admin", user.ID), "user is not an admin")
|
return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("user %s not an admin", user.ID), "user is not an admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
data := func(innerCtx context.Context) (io.Reader, int, error) {
|
data := func(innerCtx context.Context) (io.Reader, int, error) {
|
||||||
|
|
|
@ -23,12 +23,13 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ApplicationCreateRequest) (*apimodel.Application, error) {
|
func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ApplicationCreateRequest) (*apimodel.Application, gtserror.WithCode) {
|
||||||
// set default 'read' for scopes if it's not set
|
// set default 'read' for scopes if it's not set
|
||||||
var scopes string
|
var scopes string
|
||||||
if form.Scopes == "" {
|
if form.Scopes == "" {
|
||||||
|
@ -40,13 +41,13 @@ func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *api
|
||||||
// generate new IDs for this application and its associated client
|
// generate new IDs for this application and its associated client
|
||||||
clientID, err := id.NewRandomULID()
|
clientID, err := id.NewRandomULID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
clientSecret := uuid.NewString()
|
clientSecret := uuid.NewString()
|
||||||
|
|
||||||
appID, err := id.NewRandomULID()
|
appID, err := id.NewRandomULID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the application to put in the database
|
// generate the application to put in the database
|
||||||
|
@ -62,7 +63,7 @@ func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *api
|
||||||
|
|
||||||
// chuck it in the db
|
// chuck it in the db
|
||||||
if err := p.db.Put(ctx, app); err != nil {
|
if err := p.db.Put(ctx, app); err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we need to model an oauth client from the application that the oauth library can use
|
// now we need to model an oauth client from the application that the oauth library can use
|
||||||
|
@ -70,17 +71,18 @@ func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *api
|
||||||
ID: clientID,
|
ID: clientID,
|
||||||
Secret: clientSecret,
|
Secret: clientSecret,
|
||||||
Domain: form.RedirectURIs,
|
Domain: form.RedirectURIs,
|
||||||
UserID: "", // This client isn't yet associated with a specific user, it's just an app client right now
|
// This client isn't yet associated with a specific user, it's just an app client right now
|
||||||
|
UserID: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
// chuck it in the db
|
// chuck it in the db
|
||||||
if err := p.db.Put(ctx, oc); err != nil {
|
if err := p.db.Put(ctx, oc); err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiApp, err := p.tc.AppToAPIAppSensitive(ctx, app)
|
apiApp, err := p.tc.AppToAPIAppSensitive(ctx, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiApp, nil
|
return apiApp, nil
|
||||||
|
|
|
@ -42,7 +42,7 @@ func (p *processor) GetFollowers(ctx context.Context, requestedUsername string,
|
||||||
|
|
||||||
requestingAccount, err := p.federator.GetRemoteAccount(ctx, requestedUsername, requestingAccountURI, false, false)
|
requestingAccount, err := p.federator.GetRemoteAccount(ctx, requestedUsername, requestingAccountURI, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorNotAuthorized(err)
|
return nil, gtserror.NewErrorUnauthorized(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true)
|
blocked, err := p.db.IsBlocked(ctx, requestedAccount.ID, requestingAccount.ID, true)
|
||||||
|
@ -51,7 +51,7 @@ func (p *processor) GetFollowers(ctx context.Context, requestedUsername string,
|
||||||
}
|
}
|
||||||
|
|
||||||
if blocked {
|
if blocked {
|
||||||
return nil, gtserror.NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
|
return nil, gtserror.NewErrorUnauthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedAccountURI, err := url.Parse(requestedAccount.URI)
|
requestedAccountURI, err := url.Parse(requestedAccount.URI)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue