mirror of
https://github.com/offen/website.git
synced 2024-11-22 01:00:26 +01:00
normalize local urls throughout
This commit is contained in:
parent
b2fe172681
commit
460e2a77ac
@ -17,7 +17,6 @@ deploy_preconditions: &deploy_preconditions
|
|||||||
- script
|
- script
|
||||||
- auditorium
|
- auditorium
|
||||||
- packages
|
- packages
|
||||||
- shared
|
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: /^master$/
|
only: /^master$/
|
||||||
@ -68,20 +67,6 @@ jobs:
|
|||||||
cp ~/offen/bootstrap.yml .
|
cp ~/offen/bootstrap.yml .
|
||||||
make test-ci
|
make test-ci
|
||||||
|
|
||||||
shared:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:1.12
|
|
||||||
working_directory: ~/offen/shared
|
|
||||||
steps:
|
|
||||||
- checkout:
|
|
||||||
path: ~/offen
|
|
||||||
- run:
|
|
||||||
name: Install dependencies
|
|
||||||
command: go get ./...
|
|
||||||
- run:
|
|
||||||
name: Run tests
|
|
||||||
command: make test
|
|
||||||
|
|
||||||
vault:
|
vault:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:10-browsers
|
- image: circleci/node:10-browsers
|
||||||
|
6
Makefile
6
Makefile
@ -25,4 +25,8 @@ bootstrap:
|
|||||||
@echo "Bootstrapping Accounts service ..."
|
@echo "Bootstrapping Accounts service ..."
|
||||||
@docker-compose run accounts make bootstrap
|
@docker-compose run accounts make bootstrap
|
||||||
|
|
||||||
.PHONY: setup bootstrap
|
build:
|
||||||
|
@docker build -t offen-server:latest -f build/server/Dockerfile .
|
||||||
|
@docker build -t offen-proxy:latest -f build/proxy/Dockerfile .
|
||||||
|
|
||||||
|
.PHONY: setup bootstrap build
|
||||||
|
@ -2,15 +2,23 @@ version: '3'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
proxy:
|
proxy:
|
||||||
build:
|
image: offen-proxy:latest
|
||||||
context: './..'
|
|
||||||
dockerfile: build/proxy/Dockerfile
|
|
||||||
ports:
|
ports:
|
||||||
- 3000:80
|
- 3000:80
|
||||||
depends_on:
|
depends_on:
|
||||||
- server
|
- server
|
||||||
|
|
||||||
server:
|
server:
|
||||||
build:
|
image: offen-server:latest
|
||||||
context: './..'
|
environment:
|
||||||
dockerfile: build/server/Dockerfile
|
POSTGRES_CONNECTION_STRING: postgres://postgres:develop@server_database:5432/postgres?sslmode=disable
|
||||||
|
COOKIE_EXCHANGE_SECRET: Wsttdo4Z3mXV5sTc
|
||||||
|
EVENT_RETENTION_PERIOD: 4464h
|
||||||
|
ACCOUNT_USER_EMAIL_SALT: UwBkP24HCUYqy0Eq
|
||||||
|
depends_on:
|
||||||
|
- server_database
|
||||||
|
|
||||||
|
server_database:
|
||||||
|
image: postgres:11.2
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: develop
|
||||||
|
@ -50,8 +50,8 @@ RUN make publish
|
|||||||
FROM nginx:1.17-alpine
|
FROM nginx:1.17-alpine
|
||||||
|
|
||||||
COPY --from=homepage /code/homepage/output /www/data
|
COPY --from=homepage /code/homepage/output /www/data
|
||||||
|
COPY --from=script /code/script/dist /www/data
|
||||||
COPY --from=auditorium /code/auditorium/dist /www/data/auditorium
|
COPY --from=auditorium /code/auditorium/dist /www/data/auditorium
|
||||||
COPY --from=script /code/script/dist /www/data/script
|
|
||||||
COPY --from=vault /code/vault/dist /www/data/vault
|
COPY --from=vault /code/vault/dist /www/data/vault
|
||||||
COPY ./build/proxy/nginx.conf /etc/nginx/nginx.conf
|
COPY ./build/proxy/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
events {}
|
events {}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
include /etc/nginx/mime.types;
|
gzip on;
|
||||||
|
gzip_comp_level 2;
|
||||||
|
gzip_min_length 1000;
|
||||||
|
gzip_proxied expired no-cache no-store private auth;
|
||||||
|
|
||||||
|
include mime.types;
|
||||||
upstream server {
|
upstream server {
|
||||||
server server:3000;
|
server server:3000;
|
||||||
}
|
}
|
||||||
@ -16,6 +20,12 @@ http {
|
|||||||
proxy_pass http://server;
|
proxy_pass http://server;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
rewrite ^/api(.*)$ $1 break;
|
rewrite ^/api(.*)$ $1 break;
|
||||||
|
proxy_hide_header Content-Type;
|
||||||
|
add_header Content-Type "application/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
location /auditorium/ {
|
||||||
|
try_files $uri $uri/ /auditorium/index.html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ services:
|
|||||||
- ./styles:/code/styles
|
- ./styles:/code/styles
|
||||||
ports:
|
ports:
|
||||||
- 8080:80
|
- 8080:80
|
||||||
- 8081:81
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- homepage
|
- homepage
|
||||||
- server
|
- server
|
||||||
@ -26,7 +25,6 @@ services:
|
|||||||
- ./bootstrap.yml:/offen/server/bootstrap.yml
|
- ./bootstrap.yml:/offen/server/bootstrap.yml
|
||||||
- serverdeps:/go/pkg/mod
|
- serverdeps:/go/pkg/mod
|
||||||
environment:
|
environment:
|
||||||
CORS_ORIGIN: http://localhost:9977
|
|
||||||
POSTGRES_CONNECTION_STRING: postgres://postgres:develop@server_database:5432/postgres?sslmode=disable
|
POSTGRES_CONNECTION_STRING: postgres://postgres:develop@server_database:5432/postgres?sslmode=disable
|
||||||
PORT: 8080
|
PORT: 8080
|
||||||
DEVELOPMENT: '1'
|
DEVELOPMENT: '1'
|
||||||
@ -61,8 +59,6 @@ services:
|
|||||||
- .:/offen
|
- .:/offen
|
||||||
- scriptdeps:/offen/script/node_modules
|
- scriptdeps:/offen/script/node_modules
|
||||||
command: npm start -- --port 9966
|
command: npm start -- --port 9966
|
||||||
environment:
|
|
||||||
- VAULT_HOST=/vault/
|
|
||||||
|
|
||||||
auditorium:
|
auditorium:
|
||||||
build:
|
build:
|
||||||
@ -73,8 +69,6 @@ services:
|
|||||||
- .:/offen
|
- .:/offen
|
||||||
- auditoriumdeps:/offen/auditorium/node_modules
|
- auditoriumdeps:/offen/auditorium/node_modules
|
||||||
command: npm start -- --port 9955
|
command: npm start -- --port 9955
|
||||||
environment:
|
|
||||||
- VAULT_HOST=/vault/
|
|
||||||
|
|
||||||
homepage:
|
homepage:
|
||||||
build:
|
build:
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
Title: Auditorium | offen
|
|
||||||
description: offen is a free and open source analytics software for websites and web applications that allows respectful handling of data.
|
|
||||||
save_as: auditorium/index.html
|
|
||||||
href: /auditorium/
|
|
||||||
template: auditorium
|
|
@ -11,8 +11,8 @@ sys.path.append(os.curdir)
|
|||||||
from pelicanconf import *
|
from pelicanconf import *
|
||||||
|
|
||||||
# If your site is available via HTTPS, make sure SITEURL begins with https://
|
# If your site is available via HTTPS, make sure SITEURL begins with https://
|
||||||
SITEURL = 'https://www.offen.dev'
|
SITEURL = os.environ.get('PELICAN_SITEURL', 'https://www.offen.dev')
|
||||||
RELATIVE_URLS = False
|
# RELATIVE_URLS = True
|
||||||
|
|
||||||
FEED_ALL_ATOM = 'feeds/all.atom.xml'
|
FEED_ALL_ATOM = 'feeds/all.atom.xml'
|
||||||
CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'
|
CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div id="app-host"></div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
{{ super () }}
|
|
||||||
<script src="{{AUDITORIUM_SCRIPT}}"></script>
|
|
||||||
{% endblock %}
|
|
@ -20,7 +20,7 @@
|
|||||||
<link rel="canonical" href="{{ SITEURL }}/{{ page.save_as }}">
|
<link rel="canonical" href="{{ SITEURL }}/{{ page.save_as }}">
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="/theme/images/favicon.ico">
|
<link rel="shortcut icon" type="image/x-icon" href="/theme/images/favicon.ico">
|
||||||
{% assets filters="cssmin", output="css/style.min.css", "css/normalize.css", "css/fonts.css", "css/style.css" %}
|
{% assets filters="cssmin", output="css/style.min.css", "css/normalize.css", "css/fonts.css", "css/style.css" %}
|
||||||
<link rel="stylesheet" href="{{ SITEURL }}/{{ ASSET_URL }}">
|
<link rel="stylesheet" href="/{{ ASSET_URL }}">
|
||||||
{% endassets %}
|
{% endassets %}
|
||||||
{% if OFFEN_ACCOUNT_ID %}
|
{% if OFFEN_ACCOUNT_ID %}
|
||||||
<script async src="https://script-alpha.offen.dev/script.js" data-account-id="{{ OFFEN_ACCOUNT_ID }}"></script>
|
<script async src="https://script-alpha.offen.dev/script.js" data-account-id="{{ OFFEN_ACCOUNT_ID }}"></script>
|
||||||
@ -138,7 +138,7 @@
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% assets filters="rjsmin", output="scripts/packed.js", "scripts/jquery-3.4.1.min.js", "scripts/menu.js", "scripts/fade.js" %}
|
{% assets filters="rjsmin", output="scripts/packed.js", "scripts/jquery-3.4.1.min.js", "scripts/menu.js", "scripts/fade.js" %}
|
||||||
<script src="{{ SITEURL }}/{{ ASSET_URL }}"></script>
|
<script src="/{{ ASSET_URL }}"></script>
|
||||||
{% endassets %}
|
{% endassets %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
test:
|
|
||||||
@go test -race -cover ./...
|
|
@ -1,15 +0,0 @@
|
|||||||
# shared
|
|
||||||
|
|
||||||
`shared` contains Go packages shared across applications. Consumers need to symlink the folder into their app directory.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Commands
|
|
||||||
|
|
||||||
#### Run the tests
|
|
||||||
|
|
||||||
```
|
|
||||||
make test
|
|
||||||
```
|
|
@ -1,24 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type errorResponse struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
Status int `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsonError(err error, status int) []byte {
|
|
||||||
r := errorResponse{err.Error(), status}
|
|
||||||
b, _ := json.Marshal(r)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// RespondWithJSONError writes the given error to the given response writer
|
|
||||||
// while wrapping it into a JSON payload.
|
|
||||||
func RespondWithJSONError(w http.ResponseWriter, err error, status int) {
|
|
||||||
w.WriteHeader(status)
|
|
||||||
w.Write(jsonError(err, status))
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRespondWithJSONError(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
RespondWithJSONError(w, errors.New("does not work"), http.StatusInternalServerError)
|
|
||||||
if w.Code != http.StatusInternalServerError {
|
|
||||||
t.Errorf("Unexpected status code %d", w.Code)
|
|
||||||
}
|
|
||||||
if w.Body.String() != `{"error":"does not work","status":500}` {
|
|
||||||
t.Errorf("Unexpected response body %s", w.Body.String())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,174 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/lestrrat-go/jwx/jwa"
|
|
||||||
"github.com/lestrrat-go/jwx/jwt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type contextKey string
|
|
||||||
|
|
||||||
// ClaimsContextKey will be used to attach a JWT claim to a request context
|
|
||||||
const ClaimsContextKey contextKey = "claims"
|
|
||||||
|
|
||||||
// JWTProtect uses the public key located at the given URL to check if the
|
|
||||||
// cookie value is signed properly. In case yes, the JWT claims will be added
|
|
||||||
// to the request context
|
|
||||||
func JWTProtect(keyURL, cookieName, headerName string, authorizer func(*http.Request, map[string]interface{}) error, cache Cache) func(http.Handler) http.Handler {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var jwtValue string
|
|
||||||
if authCookie, err := r.Cookie(cookieName); err == nil {
|
|
||||||
jwtValue = authCookie.Value
|
|
||||||
} else {
|
|
||||||
if header := r.Header.Get(headerName); header != "" {
|
|
||||||
jwtValue = header
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if jwtValue == "" {
|
|
||||||
RespondWithJSONError(w, errors.New("jwt: could not infer JWT value from cookie or header"), http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var keys [][]byte
|
|
||||||
if cache != nil {
|
|
||||||
lookup, lookupErr := cache.Get()
|
|
||||||
if lookupErr == nil {
|
|
||||||
keys = lookup
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if keys == nil {
|
|
||||||
var keysErr error
|
|
||||||
keys, keysErr = fetchKeys(keyURL)
|
|
||||||
if keysErr != nil {
|
|
||||||
RespondWithJSONError(w, fmt.Errorf("jwt: error fetching keys: %v", keysErr), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cache != nil {
|
|
||||||
cache.Set(keys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var token *jwt.Token
|
|
||||||
var tokenErr error
|
|
||||||
// the response can contain multiple keys to try as some of them
|
|
||||||
// might have been retired with signed tokens still in use until
|
|
||||||
// their expiry
|
|
||||||
for _, key := range keys {
|
|
||||||
token, tokenErr = tryParse(key, jwtValue)
|
|
||||||
if tokenErr == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokenErr != nil {
|
|
||||||
RespondWithJSONError(w, fmt.Errorf("jwt: error verifying token signature: %v", tokenErr), http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := token.Verify(jwt.WithAcceptableSkew(0)); err != nil {
|
|
||||||
RespondWithJSONError(w, fmt.Errorf("jwt: error verifying token claims: %v", err), http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
privKey, _ := token.Get("priv")
|
|
||||||
claims, _ := privKey.(map[string]interface{})
|
|
||||||
if err := authorizer(r, claims); err != nil {
|
|
||||||
RespondWithJSONError(w, fmt.Errorf("jwt: token claims do not allow the requested operation: %v", err), http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r = r.WithContext(context.WithValue(r.Context(), ClaimsContextKey, claims))
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryParse(key []byte, tokenValue string) (*jwt.Token, error) {
|
|
||||||
keyBytes, _ := pem.Decode([]byte(key))
|
|
||||||
if keyBytes == nil {
|
|
||||||
return nil, errors.New("jwt: no PEM block found in given key")
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, parseErr := x509.ParsePKCS1PublicKey(keyBytes.Bytes)
|
|
||||||
if parseErr != nil {
|
|
||||||
return nil, fmt.Errorf("jwt: error parsing key: %v", parseErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
token, jwtErr := jwt.ParseVerify(strings.NewReader(tokenValue), jwa.RS256, pubKey)
|
|
||||||
if jwtErr != nil {
|
|
||||||
return nil, fmt.Errorf("jwt: error parsing token: %v", jwtErr)
|
|
||||||
}
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyResponse struct {
|
|
||||||
Keys []string `json:"keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchKeys(keyURL string) ([][]byte, error) {
|
|
||||||
fetchRes, fetchErr := http.Get(keyURL)
|
|
||||||
if fetchErr != nil {
|
|
||||||
return nil, fetchErr
|
|
||||||
}
|
|
||||||
defer fetchRes.Body.Close()
|
|
||||||
payload := keyResponse{}
|
|
||||||
if err := json.NewDecoder(fetchRes.Body).Decode(&payload); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
asBytes := [][]byte{}
|
|
||||||
for _, key := range payload.Keys {
|
|
||||||
asBytes = append(asBytes, []byte(key))
|
|
||||||
}
|
|
||||||
return asBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache can be implemented by consumers in order to define how requests
|
|
||||||
// for public keys are being cached. For most use cases, the default cache
|
|
||||||
// supplied by this package will suffice.
|
|
||||||
type Cache interface {
|
|
||||||
Get() ([][]byte, error)
|
|
||||||
Set([][]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
type defaultCache struct {
|
|
||||||
value *[][]byte
|
|
||||||
expires time.Duration
|
|
||||||
deadline time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultCacheExpiry should be used by cache instantiations without
|
|
||||||
// any particular requirements.
|
|
||||||
const DefaultCacheExpiry = time.Minute * 15
|
|
||||||
|
|
||||||
// ErrNoCache is returned on a cache lookup that did not yield a result
|
|
||||||
var ErrNoCache = errors.New("nothing found in cache")
|
|
||||||
|
|
||||||
func (c *defaultCache) Get() ([][]byte, error) {
|
|
||||||
if c.value != nil && time.Now().Before(c.deadline) {
|
|
||||||
return *c.value, nil
|
|
||||||
}
|
|
||||||
return nil, ErrNoCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *defaultCache) Set(value [][]byte) {
|
|
||||||
c.deadline = time.Now().Add(c.expires)
|
|
||||||
c.value = &value
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultKeyCache creates a simple cache that will hold a single
|
|
||||||
// value for the given expiration time
|
|
||||||
func NewDefaultKeyCache(expires time.Duration) Cache {
|
|
||||||
return &defaultCache{
|
|
||||||
expires: expires,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,279 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/lestrrat-go/jwx/jwa"
|
|
||||||
|
|
||||||
"github.com/lestrrat-go/jwx/jwt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const publicKey = `
|
|
||||||
-----BEGIN RSA PUBLIC KEY-----
|
|
||||||
MIIBCgKCAQEAl2ifzOsh6AMRHZBe8xycvk01s3EAGT12WbgV9Z7b420Dj3NXrkns
|
|
||||||
N/jvBbtO9cjQg4WM7NPZLs+ZutRkHCtMxt7vB0kjOYetLPcGdObsVBB5k1jvwvsJ
|
|
||||||
HkcfmSsZdrV0Lz2Yxuf6ADWkxBqAY3GsS0zW0A2nIMc+41ZxqsZa3ProsKJxecRX
|
|
||||||
SSZMpZtqCGt/S83Rek4eAahllcWfZpQmoEk7usLuUl5tH2TmaW3e5lo0JNfdwcq5
|
|
||||||
PMCa8WSZBFH3YzVttB8rbe7a7336wL2NJQFz6dswL5X1dECYpZ5TRtNgzQYa4V0W
|
|
||||||
AeICq+EzigaTxrjDHc5urHqEosz1le7O4QIDAQAB
|
|
||||||
-----END RSA PUBLIC KEY-----
|
|
||||||
`
|
|
||||||
|
|
||||||
const privateKey = `
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXaJ/M6yHoAxEd
|
|
||||||
kF7zHJy+TTWzcQAZPXZZuBX1ntvjbQOPc1euSew3+O8Fu071yNCDhYzs09kuz5m6
|
|
||||||
1GQcK0zG3u8HSSM5h60s9wZ05uxUEHmTWO/C+wkeRx+ZKxl2tXQvPZjG5/oANaTE
|
|
||||||
GoBjcaxLTNbQDacgxz7jVnGqxlrc+uiwonF5xFdJJkylm2oIa39LzdF6Th4BqGWV
|
|
||||||
xZ9mlCagSTu6wu5SXm0fZOZpbd7mWjQk193Byrk8wJrxZJkEUfdjNW20Hytt7trv
|
|
||||||
ffrAvY0lAXPp2zAvlfV0QJilnlNG02DNBhrhXRYB4gKr4TOKBpPGuMMdzm6seoSi
|
|
||||||
zPWV7s7hAgMBAAECggEAYoPOxjSP4ThtoIDZZvHNAv2V3WW/HK0jHolqsGBmznmW
|
|
||||||
AXaZLGwo6NpuG5qea8n38jupUEcfXxfw/OFJKhL6Z8OSX3k1FC+1fDZW2yWNy7zU
|
|
||||||
fg02I/XXHv5EDxM+BEFYkYxQpcs2nYBJ7tcXhpzl8DDU7JaVkfxSbPVIDEf3wyP2
|
|
||||||
k6DjYEeAj7uAsp50/32H9zhlJP/cFZaPiyFYy9/gOmDenrPVyJ/f7iQYNwYAwdbt
|
|
||||||
yfp11Wd5BpePR58+YXICE8oBtzHvB50akK6RZULC3xHVxLQQ1bSxx6vnttxw5RW+
|
|
||||||
QRHTVWtRyWiKe/l5jMvVSUo5XCLqsL2iXfR4bz6hyQKBgQDJQVWEGHyD6JyaN/6i
|
|
||||||
5M5+O/YTbzMBgt1JAuVR2c0HYE4LpgrX4cA4kT3Bsa/Z9o0uuWVuxVab5gLsjsDu
|
|
||||||
EI4o+HJQ3pl4LF9xqrndTdybwmZAT6jv3rM/VGfaCDzXCPzVx169I+WsfkyGW7Tr
|
|
||||||
Cj4KDZyykruG/9OrpN1Aeq9a3wKBgQDAmCnQHLEPvZdezJGBc34HjZntrbW67iFB
|
|
||||||
L0waCGWydyunYmzfja1FSvlSoziZdqoq0N4+uBQFPZIlERvq0zMgIzNFvxt/WlFV
|
|
||||||
kQcBV24MNa8dtd+P7GDY8TzTfYBeXwJoi5c59sWLzSwpTlw0rI+ZvuA66eEF7xih
|
|
||||||
eWw3k1jOPwKBgQCKf8DHGEbQTEtBQlmlZkrIyqDs/PCgEJwSe8Cu1HFpqxfqokkC
|
|
||||||
CiTLiQB0BMEdAbRlPEcWtQ2GWgMXIqKY8qGyhk+9YYNCFV9VjQU9zDCOrHjLt0Zu
|
|
||||||
VNcMNR0HCfY8kb3VrM+A4GxVidFGAWR+/9xz9KwqpBoTrIjRrbJphkSZBwKBgBMs
|
|
||||||
0zTqNmLH0JNasL3/vrOH0KSOYAKdhOgVinEpFt7+6HTA4vAbDf5RKaOlppP48ZZT
|
|
||||||
t1ztPOkMqUlRe8MUhgmUF53BGj7CwkhPqS/kAYvrqGS/3+NXeIkA87pmy2oZ8YZx
|
|
||||||
J3xY6nAx3Ey8hYelCqMXEwIqmQHbPUuOaEzcOcJHAoGANw0TRha2YSbUqS3HiGR/
|
|
||||||
Jm0lNfeLc3cYlEq0ggRDPLD11485rxVKnaKVHGPYW28OQ4jA+GCc7VZCfPV8VQXW
|
|
||||||
6b+jUnnBwu/KuYvGMee/xJv1c4MTG54mR9UrUt+R80S0OplpcYkcQnft2Bi+AZ1h
|
|
||||||
5aZTE7XIouXCYiMKPl4AMtI=
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestJWTProtect(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
cookie *http.Cookie
|
|
||||||
headers *http.Header
|
|
||||||
server *httptest.Server
|
|
||||||
authorizer func(r *http.Request, claims map[string]interface{}) error
|
|
||||||
expectedStatusCode int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"no cookie",
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusForbidden,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bad server",
|
|
||||||
&http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: "irrelevantgibberish",
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusInternalServerError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"non-json response",
|
|
||||||
&http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: "irrelevantgibberish",
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("here's some bytes 4 y'all"))
|
|
||||||
})),
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusInternalServerError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bad key value",
|
|
||||||
&http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: "irrelevantgibberish",
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte(`{"keys":["not really a key","me neither"]}`))
|
|
||||||
})),
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusForbidden,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"invalid key",
|
|
||||||
&http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: "irrelevantgibberish",
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte(`{"keys":["-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCATZAMIIBCgKCAQEA2yUfHH6SRYKvBTemrefi\nHk4L4qkcc4skl4QCaHOkfgA4VcGKG2nXysYuZK7AzNOcHQVi+e4BwN+BfIZtwEU5\n7Ogctb5eg8ksxxLjS7eSRfQIvPGfAbJ12R9OoOWcue/CdUy/YMec4R/o4+tZ45S6\nQQWIMhLqYljw+s1Runda3K8Q8lOdJ4yEZckXaZr1waNJikC7oGpT7ClAgdbvWIbo\nN18G1OluRn+3WNdcN6V+vIj8c9dGs92bgTPX4cn3RmB/80BDfzeFiPMRw5xaq66F\n42zXzllkTqukQPk2wmO5m9pFy0ciRve+awfgbTtZRZOEpTSWLbbpOfd4RQ5YqDWJ\nmQIDAQAB\n-----END PUBLIC KEY-----"]}`))
|
|
||||||
})),
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusForbidden,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"valid key, bad auth token",
|
|
||||||
&http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: "irrelevantgibberish",
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte(
|
|
||||||
fmt.Sprintf(`{"keys":["%s"]}`, strings.ReplaceAll(publicKey, "\n", `\n`)),
|
|
||||||
))
|
|
||||||
})),
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusForbidden,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"valid key, valid token",
|
|
||||||
&http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: (func() string {
|
|
||||||
token := jwt.New()
|
|
||||||
token.Set("exp", time.Now().Add(time.Hour))
|
|
||||||
keyBytes, _ := pem.Decode([]byte(privateKey))
|
|
||||||
privKey, _ := x509.ParsePKCS8PrivateKey(keyBytes.Bytes)
|
|
||||||
b, _ := token.Sign(jwa.RS256, privKey)
|
|
||||||
return string(b)
|
|
||||||
})(),
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte(
|
|
||||||
fmt.Sprintf(`{"keys":["%s"]}`, strings.ReplaceAll(publicKey, "\n", `\n`)),
|
|
||||||
))
|
|
||||||
})),
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusOK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ok token in headers",
|
|
||||||
nil,
|
|
||||||
(func() *http.Header {
|
|
||||||
token := jwt.New()
|
|
||||||
token.Set("exp", time.Now().Add(time.Hour))
|
|
||||||
keyBytes, _ := pem.Decode([]byte(privateKey))
|
|
||||||
privKey, _ := x509.ParsePKCS8PrivateKey(keyBytes.Bytes)
|
|
||||||
b, _ := token.Sign(jwa.RS256, privKey)
|
|
||||||
return &http.Header{
|
|
||||||
"X-RPC-Authentication": []string{string(b)},
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte(
|
|
||||||
fmt.Sprintf(`{"keys":["%s"]}`, strings.ReplaceAll(publicKey, "\n", `\n`)),
|
|
||||||
))
|
|
||||||
})),
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusOK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bad token in headers",
|
|
||||||
nil,
|
|
||||||
(func() *http.Header {
|
|
||||||
return &http.Header{
|
|
||||||
"X-RPC-Authentication": []string{"nilly willy"},
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte(
|
|
||||||
fmt.Sprintf(`{"keys":["%s"]}`, strings.ReplaceAll(publicKey, "\n", `\n`)),
|
|
||||||
))
|
|
||||||
})),
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusForbidden,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"authorizer rejects",
|
|
||||||
&http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: (func() string {
|
|
||||||
token := jwt.New()
|
|
||||||
token.Set("exp", time.Now().Add(time.Hour))
|
|
||||||
token.Set("priv", map[string]interface{}{"ok": false})
|
|
||||||
keyBytes, _ := pem.Decode([]byte(privateKey))
|
|
||||||
privKey, _ := x509.ParsePKCS8PrivateKey(keyBytes.Bytes)
|
|
||||||
b, _ := token.Sign(jwa.RS256, privKey)
|
|
||||||
return string(b)
|
|
||||||
})(),
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte(
|
|
||||||
fmt.Sprintf(`{"keys":["%s"]}`, strings.ReplaceAll(publicKey, "\n", `\n`)),
|
|
||||||
))
|
|
||||||
})),
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error {
|
|
||||||
if claims["ok"] == true {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("expected ok to be true")
|
|
||||||
},
|
|
||||||
http.StatusForbidden,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"valid key, expired token",
|
|
||||||
&http.Cookie{
|
|
||||||
Name: "auth",
|
|
||||||
Value: (func() string {
|
|
||||||
token := jwt.New()
|
|
||||||
token.Set("exp", time.Now().Add(-time.Hour))
|
|
||||||
keyBytes, _ := pem.Decode([]byte(privateKey))
|
|
||||||
privKey, _ := x509.ParsePKCS8PrivateKey(keyBytes.Bytes)
|
|
||||||
b, _ := token.Sign(jwa.RS256, privKey)
|
|
||||||
return string(b)
|
|
||||||
})(),
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte(
|
|
||||||
fmt.Sprintf(`{"keys":["%s"]}`, strings.ReplaceAll(publicKey, "\n", `\n`)),
|
|
||||||
))
|
|
||||||
})),
|
|
||||||
func(r *http.Request, claims map[string]interface{}) error { return nil },
|
|
||||||
http.StatusForbidden,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
var url string
|
|
||||||
if test.server != nil {
|
|
||||||
url = test.server.URL
|
|
||||||
}
|
|
||||||
wrappedHandler := JWTProtect(url, "auth", "X-RPC-Authentication", test.authorizer, nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("OK"))
|
|
||||||
}))
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
if test.cookie != nil {
|
|
||||||
r.AddCookie(test.cookie)
|
|
||||||
}
|
|
||||||
if test.headers != nil {
|
|
||||||
for key, value := range *test.headers {
|
|
||||||
r.Header.Add(key, value[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wrappedHandler.ServeHTTP(w, r)
|
|
||||||
if w.Code != test.expectedStatusCode {
|
|
||||||
t.Errorf("Unexpected status code %v", w.Code)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CorsMiddleware ensures the wrapped handler will respond with proper CORS
|
|
||||||
// headers using the given origin.
|
|
||||||
func CorsMiddleware(origin string) func(http.Handler) http.Handler {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "POST,GET")
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContentTypeMiddleware ensuresthe wrapped handler will respond with a
|
|
||||||
// content type header of the given value.
|
|
||||||
func ContentTypeMiddleware(contentType string) func(http.Handler) http.Handler {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Add("Content-Type", contentType)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptoutMiddleware drops all requests to the given handler that are sent with
|
|
||||||
// a cookie of the given name,
|
|
||||||
func OptoutMiddleware(cookieName string) func(http.Handler) http.Handler {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if _, err := r.Cookie(cookieName); err == nil {
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserCookieMiddleware ensures a cookie of the given name is present and
|
|
||||||
// attaches its value to the request's context using the given key, before
|
|
||||||
// passing it on to the wrapped handler.
|
|
||||||
func UserCookieMiddleware(cookieKey string, contextKey interface{}) func(http.Handler) http.Handler {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
c, err := r.Cookie(cookieKey)
|
|
||||||
if err != nil {
|
|
||||||
RespondWithJSONError(w, errors.New("user cookie: received no or blank identifier"), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r = r.WithContext(
|
|
||||||
context.WithValue(r.Context(), contextKey, c.Value),
|
|
||||||
)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCorsMiddleware(t *testing.T) {
|
|
||||||
t.Run("default", func(t *testing.T) {
|
|
||||||
wrapped := CorsMiddleware("https://www.example.net")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("OK"))
|
|
||||||
}))
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
wrapped.ServeHTTP(w, r)
|
|
||||||
if h := w.Header().Get("Access-Control-Allow-Origin"); h != "https://www.example.net" {
|
|
||||||
t.Errorf("Unexpected header value %v", h)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContentTypeMiddleware(t *testing.T) {
|
|
||||||
t.Run("default", func(t *testing.T) {
|
|
||||||
wrapped := ContentTypeMiddleware("application/json")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("OK"))
|
|
||||||
}))
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
wrapped.ServeHTTP(w, r)
|
|
||||||
if h := w.Header().Get("Content-Type"); h != "application/json" {
|
|
||||||
t.Errorf("Unexpected header value %v", h)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOptoutMiddleware(t *testing.T) {
|
|
||||||
wrapped := OptoutMiddleware("optout")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("hey there"))
|
|
||||||
}))
|
|
||||||
t.Run("with header", func(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
r.AddCookie(&http.Cookie{
|
|
||||||
Name: "optout",
|
|
||||||
})
|
|
||||||
wrapped.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
if w.Code != http.StatusNoContent {
|
|
||||||
t.Errorf("Unexpected status code %d", w.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.Body.String() != "" {
|
|
||||||
t.Errorf("Unexpected response body %s", w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.Run("without header", func(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
wrapped.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Errorf("Unexpected status code %d", w.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.Body.String() != "hey there" {
|
|
||||||
t.Errorf("Unexpected response body %s", w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserCookieMiddleware(t *testing.T) {
|
|
||||||
wrapped := UserCookieMiddleware("user", 1)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
value := r.Context().Value(1)
|
|
||||||
fmt.Fprintf(w, "value is %v", value)
|
|
||||||
}))
|
|
||||||
t.Run("no cookie", func(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
wrapped.ServeHTTP(w, r)
|
|
||||||
if w.Code != http.StatusBadRequest {
|
|
||||||
t.Errorf("Unexpected status code %v", w.Code)
|
|
||||||
}
|
|
||||||
if !strings.Contains(w.Body.String(), "received no or blank identifier") {
|
|
||||||
t.Errorf("Unexpected body %s", w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("no value", func(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
wrapped.ServeHTTP(w, r)
|
|
||||||
r.AddCookie(&http.Cookie{
|
|
||||||
Name: "user",
|
|
||||||
Value: "",
|
|
||||||
})
|
|
||||||
if w.Code != http.StatusBadRequest {
|
|
||||||
t.Errorf("Unexpected status code %v", w.Code)
|
|
||||||
}
|
|
||||||
if !strings.Contains(w.Body.String(), "received no or blank identifier") {
|
|
||||||
t.Errorf("Unexpected body %s", w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ok", func(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
r.AddCookie(&http.Cookie{
|
|
||||||
Name: "user",
|
|
||||||
Value: "token",
|
|
||||||
})
|
|
||||||
wrapped.ServeHTTP(w, r)
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Errorf("Unexpected status code %v", w.Code)
|
|
||||||
}
|
|
||||||
if w.Body.String() != "value is token" {
|
|
||||||
t.Errorf("Unexpected body %s", w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user