From 44c6cb44f5cc321e13ecc4bc5c1676e4dbc39d60 Mon Sep 17 00:00:00 2001 From: Yifan Gu Date: Mon, 31 Aug 2015 19:24:45 -0700 Subject: [PATCH] refresh: bcrypt raw bytes rather than base64 encoded string. This enables us to control the length of the bytes that will be bcrypted, by default it's 64. Also changed the token's stored form from string('text') to []byte('bytea') and added some test cases for different types of invalid tokens. --- db/migrations/0005_refresh_token_create.sql | 2 +- db/migrations/assets.go | 6 +- db/refresh.go | 28 +++++--- functional/db_test.go | 79 ++++++++++++++++++++- refresh/refreshtest/repo.go | 4 +- refresh/repo.go | 33 ++++----- server/server_test.go | 70 +++++++++--------- 7 files changed, 153 insertions(+), 69 deletions(-) diff --git a/db/migrations/0005_refresh_token_create.sql b/db/migrations/0005_refresh_token_create.sql index acda8e29..bb413da5 100644 --- a/db/migrations/0005_refresh_token_create.sql +++ b/db/migrations/0005_refresh_token_create.sql @@ -1,7 +1,7 @@ -- +migrate Up CREATE TABLE refresh_token ( id bigint NOT NULL, - payload_hash text, + payload_hash bytea, user_id text, client_id text ); diff --git a/db/migrations/assets.go b/db/migrations/assets.go index 25173c0b..590aebfe 100644 --- a/db/migrations/assets.go +++ b/db/migrations/assets.go @@ -155,7 +155,7 @@ func dbMigrations0004_session_nonceSql() (*asset, error) { return a, nil } -var _dbMigrations0005_refresh_token_createSql = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x84\x90\x51\x4f\xc2\x30\x10\xc7\xdf\xf7\x29\xee\x0d\x88\x62\xc2\x2b\x3e\x95\xed\x0c\x8b\x5d\xa7\x5d\x2b\xf2\xb4\x54\x56\x59\xc3\x1c\x73\xad\x46\xbe\xbd\x65\x13\x08\xc4\x68\xdf\xfa\xbb\xfb\xdf\x2f\x77\xe3\x31\x5c\xbd\x99\x75\xab\x9c\x06\xd9\x04\x21\x47\x22\x10\x04\x99\x51\x84\x56\xbf\xb6\xda\x96\xb9\xdb\x6e\x74\x0d\xc3\x00\xfc\x33\x05\xbc\x98\xb5\xa9\x1d\xb0\x54\x00\x93\x94\x5e\x77\xbc\x51\xbb\x6a\xab\x8a\xbc\x54\xb6\x04\xa7\xbf\x5c\x8f\x3f\xac\x6e\x73\x9f\x39\x91\x55\x65\x74\xed\x0e\x2c\x18\xdd\x06\x07\x69\x86\x8f\x12\x59\x78\xe1\xf5\x9d\xb9\xd5\xef\x5d\x36\x13\x84\x0b\x58\xc4\x62\x0e\x93\x0e\xc4\xcc\x67\x13\x64\x02\x66\xcb\x1f\xc4\x52\x48\x62\xf6\x44\xa8\xc4\xe3\x9f\x3c\x9f\xfe\x21\x09\xe7\x08\x13\xaf\x25\x54\x20\xff\xdb\x0a\xe9\x82\x61\xb4\x1f\x7e\x56\xbd\x31\xc5\x31\xdf\x9f\x2a\x65\xf4\xa2\x07\xfa\x72\x98\x52\x99\xb0\xfd\xd9\x32\x14\x10\xe1\x1d\x91\x54\x40\xed\x57\xff\x54\xd5\x70\xf0\x9b\x74\x30\x9d\xb6\x7a\xbd\xaa\x94\xb5\xa3\x7f\x35\xdd\x4e\x24\x8a\xbc\x88\x65\x82\x93\xd8\xdf\xe2\x7c\x68\xb3\xd1\x3b\x78\xe0\x71\x42\xf8\x12\xee\x71\x09\x43\x53\xf8\xb9\xdf\x01\x00\x00\xff\xff\xff\xeb\x3d\xc4\xf9\x01\x00\x00") +var _dbMigrations0005_refresh_token_createSql = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x84\x90\x51\x4f\xc2\x30\x10\xc7\xdf\xf7\x29\xee\x0d\x88\x62\xc2\x2b\x3e\x95\xed\x0c\x8b\x5d\xa7\x5d\x2b\xf2\xb4\x14\x56\xa1\x61\x8e\xb9\x56\xe3\xbe\xbd\x65\x13\x0c\xc4\x68\xdf\xfa\xbb\xfb\xdf\x2f\x77\xe3\x31\x5c\xbd\x9a\x4d\xa3\x9c\x06\x59\x07\x21\x47\x22\x10\x04\x99\x51\x84\x46\xbf\x34\xda\x6e\x73\xb7\xdf\xe9\x0a\x86\x01\xf8\x67\x0a\x58\x99\x8d\xa9\x1c\xb0\x54\x00\x93\x94\x5e\x77\xbc\x56\x6d\xb9\x57\x45\xbe\x55\x76\x0b\xab\xd6\x69\xd5\xf3\x77\xab\x9b\xdc\x87\x9c\xfe\x74\x3d\x59\x97\x46\x57\xee\xc8\x82\xd1\x6d\x70\xb4\x66\xf8\x28\x91\x85\x17\x62\xdf\x99\x5b\xfd\xd6\x65\x33\x41\xb8\x80\x45\x2c\xe6\x30\xe9\x40\xcc\x7c\x36\x41\x26\x60\xb6\xfc\x46\x2c\x85\x24\x66\x4f\x84\x4a\x3c\xfd\xc9\xf3\xcf\x3f\x24\xe1\x1c\x61\xe2\xb5\x84\x0a\xe4\x7f\x5b\x21\x5d\x30\x8c\x0e\xc3\xcf\xaa\x37\xa6\x38\xe5\xfb\x5b\xa5\x8c\x5e\xf4\x40\x5f\x0e\x53\x2a\x13\x76\xb8\x5b\x86\x02\x22\xbc\x23\x92\x0a\xa8\xfc\xea\x1f\xaa\x1c\x0e\x7e\x93\x0e\xa6\xd3\x46\x6f\xd6\xa5\xb2\x76\xf4\xaf\xa6\xdb\x89\x44\x91\x17\xb1\x4c\x70\x12\xfb\x5b\x9c\x0f\xad\x77\xba\x85\x07\x1e\x27\x84\x2f\xe1\x1e\x97\x30\x34\x85\x9f\xfb\x15\x00\x00\xff\xff\xc4\xcc\xa8\xfd\xfa\x01\x00\x00") func dbMigrations0005_refresh_token_createSqlBytes() ([]byte, error) { return bindataRead( @@ -170,7 +170,7 @@ func dbMigrations0005_refresh_token_createSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "db/migrations/0005_refresh_token_create.sql", size: 505, mode: os.FileMode(436), modTime: time.Unix(1, 0)} + info := bindataFileInfo{name: "db/migrations/0005_refresh_token_create.sql", size: 506, mode: os.FileMode(436), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -215,7 +215,7 @@ func dbMigrations0007_session_scopeSql() (*asset, error) { return a, nil } -var _dbMigrationsAssetsGo = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x99\xdf\x6f\xdb\x46\x12\xc7\x9f\xad\xbf\x82\x35\xd0\x42\x3e\xf8\x64\xfe\x14\x45\x03\x7d\x69\x93\x03\xf2\xd0\x14\xb8\xe6\x9e\xce\x07\x61\x49\x2e\x5d\xa2\xb2\xe4\x88\x72\xcf\x49\x90\xff\xfd\xe6\xb3\x33\xb2\x9d\x58\xb2\x13\xe7\x82\xa0\x0f\x8c\xc5\xe5\xec\xec\xfc\x9e\xd9\x6f\x4e\x4e\xa2\x9f\x57\xad\x8f\xce\xfd\xd2\xaf\xdd\xc6\xb7\x51\xfd\x26\x3a\x5f\xfd\xbd\xee\x97\xad\xdb\xb8\xc9\x48\x08\x86\xd5\xd5\xba\xf1\xc3\x29\xbf\xdb\xfa\xe4\xa2\x3f\x17\xca\x7e\xb5\x1c\x4e\xe2\x38\x4e\xe6\xfd\xb2\xdf\xf4\x6e\x31\xbf\x59\x9f\x0c\xaf\x17\x3b\x69\xd3\x79\xeb\xaf\xe7\xae\xbd\xe8\xf7\xd3\x64\xf3\xab\xc1\xaf\xe7\xcd\xda\x23\xcd\xdc\x6d\xf6\x52\xe6\xf3\xc1\x0f\x83\xbc\xcd\x97\xab\x65\xe3\xf7\xd2\x15\xf3\xb5\xef\xd6\x7e\xf8\x7d\xbe\x59\xfd\xe1\x97\xc6\x7a\x2f\xf9\x54\x05\xf0\x17\xae\x5f\xcc\xaf\x96\xfd\xeb\xab\xfd\xb4\xe5\x8d\x08\x43\xb3\xba\xdc\x43\xe7\x86\xc1\x6f\x86\xc9\xf9\x8a\x4f\xcf\x7e\x8d\x5e\xfe\xfa\x2a\x7a\xfe\xec\xc5\xab\xef\x46\xa3\x4b\xd7\xfc\xe1\xce\x7d\x74\x4b\x3d\x1a\xf5\x17\x97\xab\xf5\x26\x1a\x8f\x0e\x0e\xeb\x37\x1b\x3f\x1c\xca\x8f\x66\x75\x71\x29\x1a\x0c\x27\xe7\x6f\xfb\x4b\x16\xba\x8b\x0d\x7f\xfa\x15\xff\x0e\x9b\x75\xbf\x3c\x0f\x84\xab\xf0\xef\xa6\xbf\xf0\xfa\xf9\xa4\x5f\x5d\x6d\xfa\x05\x2f\x97\x6e\xf3\xfb\x49\xd7\x2f\x3c\x3f\x0e\x47\x47\xa3\x51\x77\xb5\x6c\x22\x73\xf4\x3f\xbd\x6b\xc7\xfc\x88\xfe\xfd\x1f\x8e\x3d\x8e\x96\xee\xc2\x47\xca\xfa\x28\x1a\x6f\x57\xfd\x7a\xbd\x5a\x1f\x45\xef\x46\x07\xe7\x6f\xc3\x5b\x74\xfa\x63\x84\x54\x93\x97\xfe\xbf\x30\xf1\xeb\x71\x10\x9b\xf7\x9f\xae\xba\x4e\xde\x61\x7b\x74\x34\x3a\xe8\xbb\xb0\xe1\xbb\x1f\xa3\x65\xbf\x80\xc5\xc1\xda\x6f\xae\xd6\x4b\x5e\x8f\x23\x51\x69\xf2\x1c\xee\xdd\xf8\x10\x46\xd1\xf7\xaf\x4f\xa3\xef\xff\x3c\x54\x49\xc2\x59\xc2\xe3\xfd\x68\x74\xf0\xa7\x5b\x47\xf5\x55\x17\xe9\x39\x7a\xc8\xe8\x60\xae\xe2\xfc\x18\xf5\xab\xc9\xcf\xab\xcb\x37\xe3\x1f\x84\xe6\x58\x64\x93\x5d\xcd\xe2\xf9\x56\xd2\xc9\xcf\x8b\xd5\xe0\xc7\xa2\xfe\xff\x49\x1e\xd8\x28\xff\x3d\x8c\x84\x50\xe5\xb6\x45\x11\x6b\xf2\x13\xa2\x8f\x8f\x8e\xa1\x18\xc9\xb7\xcd\x9b\x4b\x1f\x85\x40\xc1\xe4\x57\xcd\x06\x2e\x41\x3f\xf3\x87\x1c\xb3\xec\x56\x51\xb4\x1a\x26\xff\x10\x1f\xbe\x90\x97\x9b\x7d\xe6\xc2\xed\xfa\x1d\x0e\x77\x7c\x38\x3a\x18\xfa\xb7\x3e\xea\x97\x9b\x69\x3e\x3a\xb8\x20\xe7\x8d\xd7\x2f\xf2\x3b\xac\xbc\x92\xb0\x89\x88\x9d\x09\xbf\x60\x1f\x22\x64\xdc\xf5\x1f\x1f\x71\x14\xbd\x14\xce\xe3\x23\xe3\xcd\x51\xa6\x5c\xd7\x4f\x38\x54\x36\xef\xdf\xfb\x9b\x08\x22\x7b\x83\x28\x1f\x6e\x45\xc4\x07\xb7\x22\xab\x6c\xbd\x23\xf9\x87\x0c\xd0\xeb\x31\x06\x28\x27\x3c\x6e\x14\xbd\xc7\xc1\xb4\xdf\xcf\xe4\xc5\xf0\xac\x5f\x0b\x8b\x7a\xb5\x5a\xdc\xdd\xed\x16\xc3\x23\x9a\xbf\x19\x54\x71\xbf\xee\x5c\xe3\xdf\xbd\xbf\xb3\xdb\x22\x81\xe0\x9e\xb7\xf5\x2f\x37\x15\x61\x77\x8d\xfd\xed\xf5\x42\x42\x5d\x63\x63\x7c\x78\x76\x9d\x74\x67\xd7\xb3\xfa\xec\x3a\x9e\xc9\x13\xdb\x53\x9d\x5d\x4f\xbd\xac\xdb\x5a\x27\x34\x55\x23\x4f\x76\x76\xdd\x40\xef\xce\xae\x5b\xd9\x93\xc9\xb7\x44\x9e\x66\x7a\x76\xed\x65\xbd\x94\x7d\xb1\x7c\xab\x12\xf9\x2e\xb4\x33\x59\x9f\xca\x53\xc9\x37\x27\x74\xae\x94\xf7\x56\xe8\xe4\xfb\x54\x1e\x27\x4f\x9d\x0b\xad\x7c\x2b\x0b\x7d\xcf\x84\x26\x63\x5d\xde\x53\xf8\x8a\x1c\x05\x32\xc8\xbe\x5c\x78\x26\xc2\x7f\x2a\xfc\xda\x52\xff\x16\xfc\x96\x73\x73\xa1\x4b\x84\x57\x23\xeb\x8d\x57\x99\xd8\x3f\x13\x5e\x33\x59\x2f\x44\x97\x56\xde\x3b\xd1\xc3\x23\x53\xad\xfb\x91\x2f\xf1\x6a\x87\x46\xce\x8c\x4b\x3d\x07\x79\xd0\x3d\x67\x8f\xe9\x03\x7d\xe6\xd5\x1e\xa9\xf0\xab\x38\x47\xe4\xc9\x53\xf9\x2d\x67\xe4\x9d\xea\x5a\xc3\x0f\xd9\x65\xbd\xab\xd4\xbe\x9d\xd0\x77\xb2\x56\xb7\x26\x27\xfa\x0a\xad\x97\xf3\x2a\x79\x3a\xa1\xcd\x65\x6d\xda\xa8\x1d\x3c\xb6\x90\xef\xb9\xf0\xcf\x90\x51\xde\x1b\x91\xa1\x90\xdf\x59\xa3\x74\x8d\xf0\xe9\x62\xd5\xbf\x92\xfd\xad\x53\xfb\xa3\x3b\x76\x44\x06\x74\x76\x85\xca\xee\x32\xb5\x2b\x7b\xb0\x4b\xdc\x98\x6d\xcc\x2e\xc8\x54\x6e\xfd\xd5\xa9\x1c\x31\xba\xa7\x7a\x76\x23\xb2\x37\x95\xae\x97\x46\x9f\xc7\x6a\xb3\x26\x35\x79\xe4\xbb\x6f\x35\x5e\xb0\x5d\x1d\xab\x8e\x71\xad\x71\xe3\x52\xf5\x1d\x3e\x47\x37\xd6\xe3\xa9\xc6\x46\x93\x68\x9c\x20\x17\x3a\x25\xf2\xb7\xc6\xe7\x99\xfa\x27\xc4\x06\x7b\xe5\xdc\x02\xbb\x39\xe5\x53\x0b\xcf\x54\xce\xae\x90\x9f\xb8\xad\x2c\x3e\x6b\x3d\x83\x38\xc6\xff\xb1\x7c\xf7\xb9\xfa\xaa\x26\x06\x0b\xd5\xbf\xcb\xf4\x3d\x4f\x54\x16\xe8\x90\x21\xc6\xe7\x7c\x2b\x95\x7f\xb0\xbf\xf9\x33\x11\x9a\x5a\xfe\xa6\xb1\xc6\x46\xdd\x29\x3d\xb2\x95\x42\xef\xe5\x3c\x1f\xab\xfd\xb1\x67\xd3\xe8\x1a\x39\x92\xb5\xea\x13\x72\x89\x78\x40\x26\xce\xc6\x86\xb3\xc6\xe4\x91\xf5\x59\xaa\x79\x88\xbf\x90\x1f\x7a\xf8\x63\x5b\x64\x08\xb6\xef\x34\x5f\x3a\xa1\x2b\x33\xe5\x93\x57\x6a\x93\xd8\xec\x85\x1f\x90\xa9\x90\xef\x69\xaa\xb6\x43\x2e\xe8\x39\x23\x17\x7e\x49\xad\x7c\x89\x3b\x62\x95\x1c\x49\xa9\x03\xb1\x7e\xc7\x8e\xae\xd2\x9c\x42\xef\xa2\xd1\xdc\xc1\xe7\xc9\x4c\xe3\x02\x9f\x95\x7c\x73\xaa\xef\xb4\x52\xf9\x1c\xf1\x28\x6b\xc5\xd6\xf7\x99\xf2\x22\x66\x1b\xe1\x9f\x96\x6a\x73\xe2\x93\xda\x43\x3e\x3a\xe2\x92\x5a\xd1\x6a\x7e\x40\x83\x2d\xf0\x0f\x71\x51\x78\x95\x21\x6f\x35\xce\xa9\x11\x69\xa1\xf2\x12\x97\x41\xff\x4a\xcf\xc5\x06\xc4\x25\x79\x43\xbd\x21\x9e\xa0\x0f\xe7\x4c\xd5\x27\xc8\x12\xea\xa0\xac\xf9\x46\x63\xa7\x8c\x95\x3f\xf5\x0c\x9d\xdb\x56\x65\xe2\x37\x36\xcf\x4b\xb5\x9b\xe7\x5c\xe1\x3b\x95\xb3\x3a\xa7\x35\xa8\x20\xd6\xa0\x4b\x35\xc6\xf0\x1d\x3e\x87\x07\x6b\x29\x7c\x2b\xcd\x07\xea\x21\xfe\x26\x6f\x67\x56\x4f\xe1\x8f\x0f\x1c\x74\x53\xd5\xbb\x6e\xd4\x0e\xd8\x98\x78\xe5\x0c\x7c\x48\x5d\x46\x5f\xe2\xa4\xc3\xbe\x8d\xd6\x49\x6a\x98\xcf\x34\xfe\x4b\x6a\x56\xa7\x3a\x15\xc4\x3f\xbe\x92\xf5\x7a\xa6\xfe\xa5\x2e\x84\x5e\x50\x6a\x2d\xaf\x9d\xfa\x9c\xb8\x22\x8e\xb6\xbd\x81\xda\xc7\xe3\xbc\xd6\x39\x72\x88\x5a\x4e\x3d\x8a\x8b\x2d\xdd\xe1\x76\x4a\xfc\xa4\x86\x64\x43\xcd\xae\x61\x71\x3b\xfa\xdc\x19\x36\x65\x4a\xfa\xb4\x3e\x77\x2c\x94\x87\x9f\x7a\xef\x38\x14\xea\xa3\x9b\xd1\xe5\x93\xf8\x23\xf1\xdf\xc2\xec\x75\x57\xe2\x30\x7c\xdd\x4c\xb8\x9f\xa3\xff\x63\x83\xe5\xcd\x3c\x18\x26\x3a\x61\xfe\xd1\x98\xf0\x8e\x01\xea\x34\xfa\x0c\x95\x23\xe6\xa6\xd3\x28\xc9\x66\xb3\xe3\x88\x11\xe8\xf4\xee\x84\x34\xce\xb3\xe9\x51\x58\x67\xb0\x39\xd5\xc1\xe7\x5f\xcb\xfe\x7a\x9c\x1c\x47\xf1\x91\x4c\xb0\x0e\x29\x7e\x08\x26\x78\x17\xf4\x3e\x8d\x4c\x7d\x44\x3c\x8d\xc2\x9f\xf7\x37\x5e\x74\xc7\x0f\x0d\x2b\x77\x2e\x79\x4f\x1d\x52\x68\x54\xa1\x39\x14\xda\x18\xc2\x20\xd0\x6a\x80\xa7\xd6\x4c\xd3\x46\x0b\x24\x34\xf0\x23\x11\x29\xd4\x25\x09\x94\x2b\x5f\x12\x32\x6e\xb5\x60\x41\xc3\x7b\x99\x6b\x93\x2e\x2d\xf1\xf2\x99\xf1\xaf\x94\x3f\xe7\xa4\xb5\x36\x1f\x92\x9d\x35\x9a\x27\x85\x85\x44\xa7\x38\xd1\xa0\x68\xd0\x33\xd9\x97\xda\x13\x1a\xb6\xd7\x44\x63\xb8\x08\x43\x91\xd7\x46\x16\x5b\x12\x52\x88\x29\x98\x34\x1d\x86\x11\xce\x60\x8d\x24\x86\x37\x05\x2c\xb7\xe6\x8f\x6e\x9d\x3d\x99\xed\x9b\x19\x0d\x45\x9f\xa2\x59\x34\xb7\xcd\x1d\x19\x82\x3c\x89\x26\xf5\xf6\xcc\xe9\x4c\x8b\x08\x32\x33\xb0\xd0\x28\x0a\x1b\xc2\x28\x9e\x61\x70\xc3\x7e\x4e\x8b\x35\xfa\x51\xb0\xa0\xa5\x99\xd3\x04\x28\x4e\x9c\x11\xa7\xf7\x0b\x08\x32\xd5\xd6\x78\x43\xa1\xf7\x77\x7c\xbb\xb7\x80\x7c\x18\x24\x5f\x5c\x38\x3e\x64\xb7\xbb\x60\x7c\x04\x3e\x3c\x58\x28\x3e\xe4\xf7\x84\x02\xb1\x53\xbf\xaf\x56\x18\xee\xa9\xb6\x2d\x08\xe9\xf4\xdb\xd7\x83\x7b\x80\xce\x5f\xa5\x2a\xc0\x2b\xb5\x51\x99\xf7\x90\xad\x9d\xae\x71\x4d\x49\x8d\x07\xad\x98\xfd\x61\x6f\xa2\xef\x5c\x11\x68\xe1\x9c\xd7\x15\xfa\x14\x96\x71\x41\x4e\x6f\x99\x5e\x68\xe6\xe5\x36\xee\x72\xf5\xe0\x5c\xf8\xc0\x3b\x54\x27\x67\x95\xc9\xc6\xcf\xd4\xc6\x06\x6f\x57\x13\x32\x1d\x7d\x82\x1c\x76\x1d\x60\x7c\x6c\xe5\x49\x52\x1d\x83\xa8\x00\x64\xbe\x73\x56\x3d\xa6\x3a\xa6\x33\xb6\x55\x36\xbe\x27\xf1\xfd\xec\x4e\xad\x3a\x7a\xbb\xca\x70\xfd\x78\x3c\xbb\x77\xb9\xfc\x8b\x73\x7c\x17\xd3\xdd\x99\xbe\x13\x42\x7c\x30\xdf\x77\xf1\x7e\x42\xd6\x3f\xa0\xf7\x57\xcb\xfd\x3d\xca\x6e\x2b\x40\x92\x7c\xfb\x0a\xf0\x11\x50\xfb\x57\xc8\x7f\xe2\x7e\x9b\xe3\xe4\x6a\x38\xcb\x72\x90\x3c\xfe\xdc\x9c\x27\x87\x9b\xda\x26\x8a\x5a\x79\x73\x0e\x32\x84\xb3\x2a\xbd\x22\xf0\x7b\x9b\xdb\xa1\xdb\xe6\xf7\x73\x92\x2b\x20\x30\x02\xd7\x25\xec\xc6\x75\xe8\xf1\x9c\xbc\xef\x84\x2f\xce\xc8\xfb\x2c\x77\xe7\xe3\x0e\xa0\xfe\xc1\x6c\xbc\xcf\xf7\x09\xb9\xb8\x57\xdf\xaf\x96\x89\x3b\xd5\xb4\x3c\x9c\xc6\xdf\x3e\x0d\x77\xff\x3f\xc8\x53\xb3\x91\xcb\x70\x15\x6b\xf4\xd3\xb5\x00\x3d\x6e\x80\x44\x2e\xaf\x9d\x66\x03\xf3\x2d\xe0\x1f\x19\xc7\xfe\x69\xaa\xb4\x74\x31\xe6\x64\xe6\x52\x80\xa6\xd8\xc0\x10\x66\xc8\x30\x77\xda\x1c\x4e\x56\x32\xa3\x16\x06\x70\x01\xdc\x00\xe0\x00\x1e\x30\x97\x92\x35\x01\xa0\xf2\x7a\xf9\xe6\x22\x0b\x30\x80\xfc\x80\x1a\xcc\xbf\x41\x16\xa7\x17\xfe\xae\xd6\xf7\xd4\x32\x89\x8b\x35\x97\x74\x66\xe9\xda\x2e\xf3\x01\xc4\x34\x20\x85\x0b\x3b\x80\x08\x99\x98\x1a\x58\x00\xf8\x12\xba\x65\xae\xf4\x45\x72\x0b\xc0\x01\x20\xd5\x53\x05\xf1\xb8\xf0\xd7\x06\x62\x61\x3f\x2a\x06\x76\x40\xfe\x60\x47\x00\x8a\x4c\xa7\x00\x40\x81\x6a\xa6\x17\xf8\x70\x47\x68\x15\x74\x2b\xac\x02\x00\x0e\x00\x78\x01\x98\x20\x77\x6d\x53\x02\x3a\x01\x58\x38\x03\xe6\xc2\x7a\xae\x32\xc4\x36\xc3\x23\x13\xfa\xcd\x3a\x05\x23\x5d\x63\xf2\x64\x0a\x6a\x71\xbf\xa8\xac\xf2\x61\x3f\xe4\x0c\x3e\xf1\x5a\x51\x01\x86\x00\x3c\x02\xa8\x56\x2a\x20\x04\x28\x0c\x3f\x26\x8c\xc2\x40\x88\x00\x3a\x71\x3f\x6a\x4d\xef\x42\x41\x08\x7c\x19\xc0\xcf\x4c\xed\xc4\xb4\x51\x6c\xfd\x93\xea\x39\xe8\xc7\x5f\xd6\x02\xe8\x54\x2a\x3d\x80\x0c\x95\x30\x80\x54\xb5\x01\xbc\xf9\xed\x74\xc4\x7e\x2a\x6f\x8e\xae\xc6\xd3\x6f\xf5\x6a\xf4\x6f\x67\xf7\x22\xe2\x38\x80\x98\x33\xe5\x0d\x38\xca\x7d\x07\x7b\xa5\xdb\x2e\x53\xeb\x44\xe5\x2b\x03\xee\x0c\xdc\xe6\x5c\xee\x36\x85\x81\xbf\xc4\x0c\x00\x15\x7f\x5b\x03\x8c\x99\xe4\xb0\x1f\x9d\xc6\x19\x70\x47\xdc\xd1\x7d\xf0\x6d\x61\x77\xc8\xda\x80\x70\xe2\x0a\x10\x89\xbb\x16\xf1\x84\xfd\x12\x03\xaa\x90\x09\x60\x10\x3f\x00\xf4\x21\x1b\x6b\x01\x98\x8c\x15\x24\x02\xfc\xc6\x57\xe4\x5d\xb0\x39\x20\x98\x53\xf9\x98\xf4\x02\x40\x6a\x80\x1c\xc0\x51\x66\xc0\x55\xe8\x6c\xf9\x6d\xbc\x84\xbc\x2c\x54\x5f\xfc\xd0\x5a\xce\x30\x5d\x02\x10\x93\x43\xdc\xf1\x98\x28\x03\xa0\x66\xa0\x28\xdd\x0f\xd0\x1c\xa0\x31\x4c\x9a\xde\xba\x6b\x65\xf7\xcb\x4c\xbf\x85\x58\xe8\xec\x0e\xf9\x51\x37\xe3\x09\x00\x5b\x7b\x0b\x3c\xdf\xd2\xed\xeb\x66\x7b\x6b\xd9\x17\x37\xb5\xbd\x9c\x77\xf7\xb6\xfd\xff\xb9\xfc\x60\x8b\xdb\x7b\xca\x13\x3a\xdd\x63\xb6\x78\xb0\xe1\xfd\x2f\x00\x00\xff\xff\xfb\x08\x01\x0a\x00\x20\x00\x00") +var _dbMigrationsAssetsGo = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x99\xdf\x6f\xdb\x46\x12\xc7\x9f\xad\xbf\x82\x35\xd0\x42\x3a\xf8\x64\x92\xe2\x2f\x19\xe8\x4b\xdb\x1c\x90\x87\xa6\xc0\x35\xf7\x74\x3e\x08\x4b\x72\xe9\x12\x95\x25\x47\x94\x7b\x4e\x82\xfc\xef\x37\x9f\x9d\x91\xeb\xc4\x92\x9d\x38\x17\x04\x7d\x60\x2c\x2e\x67\x67\xe7\xf7\xcc\x7e\x73\x7a\x1a\xfd\xb8\x6e\x7d\x74\xe1\x57\x7e\xe3\xb6\xbe\x8d\xea\xd7\xd1\xc5\xfa\xef\x75\xbf\x6a\xdd\xd6\x4d\x47\x42\x30\xac\xaf\x37\x8d\x1f\xce\xf8\xdd\xd6\xa7\x97\xfd\x85\x50\xf6\xeb\xd5\x70\x1a\xc7\x71\xb2\xe8\x57\xfd\xb6\x77\xcb\xc5\xed\xfa\x74\x78\xb5\xdc\x4b\x9b\x2e\x5a\x7f\xb3\x70\xed\x65\x7f\x98\x66\xb6\xb8\x1e\xfc\x66\xd1\x6c\x3c\xd2\x2c\xdc\xf6\x20\x65\xb6\x18\xfc\x30\xc8\xdb\x62\xb5\x5e\x35\xfe\x20\x5d\xbe\xd8\xf8\x6e\xe3\x87\xdf\x16\xdb\xf5\xef\x7e\x65\xac\x0f\x92\x17\x2a\x80\xbf\x74\xfd\x72\x71\xbd\xea\x5f\x5d\x1f\xa6\x2d\x6f\x45\x18\x9a\xf5\xd5\x01\x3a\x37\x0c\x7e\x3b\x4c\x2f\xd6\x7c\xfa\xe9\x97\xe8\xc5\x2f\x2f\xa3\x67\x3f\x3d\x7f\xf9\xcd\x68\x74\xe5\x9a\xdf\xdd\x85\x8f\xfe\xa4\x1e\x8d\xfa\xcb\xab\xf5\x66\x1b\x8d\x47\x47\xc7\xf5\xeb\xad\x1f\x8e\xe5\x47\xb3\xbe\xbc\x12\x0d\x86\xd3\x8b\x37\xfd\x15\x0b\xdd\xe5\x96\x3f\xfd\x9a\x7f\x87\xed\xa6\x5f\x5d\x04\xc2\x75\xf8\x77\xdb\x5f\x7a\xfd\x7c\xda\xaf\xaf\xb7\xfd\x92\x97\x2b\xb7\xfd\xed\xb4\xeb\x97\x9e\x1f\xc7\xa3\xc9\x68\xd4\x5d\xaf\x9a\xc8\x1c\xfd\x4f\xef\xda\x31\x3f\xa2\x7f\xff\x87\x63\x4f\xa2\x95\xbb\xf4\x91\xb2\x9e\x44\xe3\xdd\xaa\xdf\x6c\xd6\x9b\x49\xf4\x76\x74\x74\xf1\x26\xbc\x45\x67\xdf\x47\x48\x35\x7d\xe1\xff\x0b\x13\xbf\x19\x07\xb1\x79\xff\xe1\xba\xeb\xe4\x1d\xb6\x93\xc9\xe8\xa8\xef\xc2\x86\x6f\xbe\x8f\x56\xfd\x12\x16\x47\x1b\xbf\xbd\xde\xac\x78\x3d\x89\x44\xa5\xe9\x33\xb8\x77\xe3\x63\x18\x45\xdf\xbe\x3a\x8b\xbe\xfd\xe3\x58\x25\x09\x67\x09\x8f\x77\xa3\xd1\xd1\x1f\x6e\x13\xd5\xd7\x5d\xa4\xe7\xe8\x21\xa3\xa3\x85\x8a\xf3\x7d\xd4\xaf\xa7\x3f\xae\xaf\x5e\x8f\xbf\x13\x9a\x13\x91\x4d\x76\x35\xcb\x67\x3b\x49\xa7\x3f\x2e\xd7\x83\x1f\x8b\xfa\xff\x27\x79\x60\xa3\xfc\x0f\x30\x12\x42\x95\xdb\x16\x45\xac\xe9\x0f\x88\x3e\x9e\x9c\x40\x31\x92\x6f\xdb\xd7\x57\x3e\x0a\x81\x82\xc9\xaf\x9b\x2d\x5c\x82\x7e\xe6\x0f\x39\x66\xd5\xad\xa3\x68\x3d\x4c\xff\x21\x3e\x7c\x2e\x2f\xb7\xfb\xcc\x85\xbb\xf5\x3b\x1c\xee\xf8\x70\x74\x34\xf4\x6f\x7c\xd4\xaf\xb6\x45\x36\x3a\xba\x24\xe7\x8d\xd7\xcf\xf2\x3b\xac\xbc\x94\xb0\x89\x88\x9d\x29\xbf\x60\x1f\x22\x64\xdc\xf5\x1f\x1e\x31\x89\x5e\x08\xe7\xf1\xc4\x78\x73\x94\x29\xd7\xf5\x53\x0e\x95\xcd\x87\xf7\xfe\x2a\x82\xc8\xde\x20\xca\xfb\x5b\x11\xf1\xc1\xad\xc8\x2a\x5b\xef\x48\xfe\x3e\x03\xf4\x7a\x8c\x01\xca\x09\x8f\x5b\x45\xef\x71\x30\xed\x0f\x33\x79\x3e\xfc\xd4\x6f\x84\x45\xbd\x5e\x2f\xef\xee\x76\xcb\xe1\x11\xcd\x5f\x0f\xaa\xb8\xdf\x74\xae\xf1\x6f\xdf\xdd\xd9\x6d\x91\x40\x70\x2f\xda\xfa\xe7\xdb\x8a\xb0\xbf\xc6\xfe\xfa\x6a\x29\xa1\xae\xb1\x31\x3e\x3e\xbf\x49\xba\xf3\x9b\xaa\x3e\xbf\x89\x2b\x79\x62\x7b\xe6\xe7\x37\x85\x97\x75\x5b\xeb\x84\x66\xde\xc8\x33\x3b\xbf\x69\xa0\x77\xe7\x37\xad\xec\x99\xc9\xb7\x44\x9e\xa6\x38\xbf\xf1\xb2\x5e\xca\xbe\x58\xbe\xcd\x13\xf9\x2e\xb4\x95\xac\x17\xf2\xcc\xe5\x9b\x13\x3a\x57\xca\x7b\x2b\x74\xf2\xbd\x90\xc7\xc9\x53\x67\x42\x2b\xdf\xca\x5c\xdf\x67\x42\x33\x63\x5d\xde\x53\xf8\x8a\x1c\x39\x32\xc8\xbe\x4c\x78\x26\xc2\xbf\x10\x7e\x6d\xa9\x7f\x73\x7e\xcb\xb9\x99\xd0\x25\xc2\xab\x91\xf5\xc6\xab\x4c\xec\xaf\x84\x57\x25\xeb\xb9\xe8\xd2\xca\x7b\x27\x7a\x78\x64\xaa\x75\x3f\xf2\x25\x5e\xed\xd0\xc8\x99\x71\xa9\xe7\x20\x0f\xba\x67\xec\x31\x7d\xa0\x9f\x79\xb5\x47\x2a\xfc\xe6\x9c\x23\xf2\x64\xa9\xfc\x96\x33\xb2\x4e\x75\xad\xe1\x87\xec\xb2\xde\xcd\xd5\xbe\x9d\xd0\x77\xb2\x56\xb7\x26\x27\xfa\x0a\xad\x97\xf3\xe6\xf2\x74\x42\x9b\xc9\x5a\xd1\xa8\x1d\x3c\xb6\x90\xef\x99\xf0\x9f\x21\xa3\xbc\x37\x22\x43\x2e\xbf\x67\x8d\xd2\x35\xc2\xa7\x8b\x55\xff\xb9\xec\x6f\x9d\xda\x1f\xdd\xb1\x23\x32\xa0\xb3\xcb\x55\x76\x37\x53\xbb\xb2\x07\xbb\xc4\x8d\xd9\xc6\xec\x82\x4c\xe5\xce\x5f\x9d\xca\x11\xa3\x7b\xaa\x67\x37\x22\x7b\x33\xd7\xf5\xd2\xe8\xb3\x58\x6d\xd6\xa4\x26\x8f\x7c\xf7\xad\xc6\x0b\xb6\xab\x63\xd5\x31\xae\x35\x6e\x5c\xaa\xbe\xc3\xe7\xe8\xc6\x7a\x5c\x68\x6c\x34\x89\xc6\x09\x72\xa1\x53\x22\x7f\x6b\x7c\x3e\x53\xff\x84\xd8\x60\xaf\x9c\x9b\x63\x37\xa7\x7c\x6a\xe1\x99\xca\xd9\x73\xe4\x27\x6e\xe7\x16\x9f\xb5\x9e\x41\x1c\xe3\xff\x58\xbe\xfb\x4c\x7d\x55\x13\x83\xb9\xea\xdf\xcd\xf4\x3d\x4b\x54\x16\xe8\x90\x21\xc6\xe7\x7c\x2b\x95\x7f\xb0\xbf\xf9\x33\x11\x9a\x5a\xfe\xa6\xb1\xc6\x46\xdd\x29\x3d\xb2\x95\x42\xef\xe5\x3c\x1f\xab\xfd\xb1\x67\xd3\xe8\x1a\x39\x32\x6b\xd5\x27\xe4\x12\xf1\x80\x4c\x9c\x8d\x0d\xab\xc6\xe4\x91\xf5\x2a\xd5\x3c\xc4\x5f\xc8\x0f\x3d\xfc\xb1\x2d\x32\x04\xdb\x77\x9a\x2f\x9d\xd0\x95\x33\xe5\x93\xcd\xd5\x26\xb1\xd9\x0b\x3f\x20\x53\x2e\xdf\xd3\x54\x6d\x87\x5c\xd0\x73\x46\x26\xfc\x92\x5a\xf9\x12\x77\xc4\x2a\x39\x92\x52\x07\x62\xfd\x8e\x1d\xdd\x5c\x73\x0a\xbd\xf3\x46\x73\x07\x9f\x27\x95\xc6\x05\x3e\x2b\xf9\xe6\x54\xdf\x62\xae\xf2\x39\xe2\x51\xd6\xf2\x9d\xef\x67\xca\x8b\x98\x6d\x84\x7f\x5a\xaa\xcd\x89\x4f\x6a\x0f\xf9\xe8\x88\x4b\x6a\x45\xab\xf9\x01\x0d\xb6\xc0\x3f\xc4\x45\xee\x55\x86\xac\xd5\x38\xa7\x46\xa4\xb9\xca\x4b\x5c\x06\xfd\xe7\x7a\x2e\x36\x20\x2e\xc9\x1b\xea\x0d\xf1\x04\x7d\x38\xa7\x50\x9f\x20\x4b\xa8\x83\xb2\xe6\x1b\x8d\x9d\x32\x56\xfe\xd4\x33\x74\x6e\x5b\x95\x89\xdf\xd8\x3c\x2b\xd5\x6e\x9e\x73\x85\x6f\x21\x67\x75\x4e\x6b\x50\x4e\xac\x41\x97\x6a\x8c\xe1\x3b\x7c\x0e\x0f\xd6\x52\xf8\xce\x35\x1f\xa8\x87\xf8\x9b\xbc\xad\xac\x9e\xc2\x1f\x1f\x38\xe8\x0a\xd5\xbb\x6e\xd4\x0e\xd8\x98\x78\xe5\x0c\x7c\x48\x5d\x46\x5f\xe2\xa4\xc3\xbe\x8d\xd6\x49\x6a\x98\x9f\x69\xfc\x97\xd4\xac\x4e\x75\xca\x89\x7f\x7c\x25\xeb\x75\xa5\xfe\xa5\x2e\x84\x5e\x50\x6a\x2d\xaf\x9d\xfa\x9c\xb8\x22\x8e\x76\xbd\x81\xda\xc7\xe3\xbc\xd6\x39\x72\x88\x5a\x4e\x3d\x8a\xf3\x1d\xdd\xf1\x6e\x4a\xfc\xa8\x86\x64\x43\xcd\xbe\x61\x71\x37\xfa\xdc\x19\x36\x65\x4a\xfa\xb8\x3e\x77\x22\x94\xc7\x1f\x7b\xef\x38\x16\xea\xc9\xed\xe8\xf2\x51\xfc\x91\xf8\x6f\x61\xf6\xba\x2b\x71\x18\xbe\x6e\x27\xdc\x4f\xd1\xff\xb1\xc1\xf2\x76\x1e\x0c\x13\x9d\x30\xff\x60\x4c\x78\xcb\x00\x75\x16\x7d\x82\xca\x11\x73\xd3\x59\x94\xcc\xaa\xea\x24\x62\x04\x3a\xbb\x3b\x21\x8d\xb3\x59\x31\x09\xeb\x0c\x36\x67\x3a\xf8\xfc\x6b\xd5\xdf\x8c\x93\x93\x28\x9e\xc8\x04\xeb\x90\xe2\xbb\x60\x82\xb7\x41\xef\xb3\xc8\xd4\x47\xc4\xb3\x28\xfc\x79\x77\xeb\x45\x77\xf2\xd0\xb0\x72\xe7\x92\xf7\xd4\x21\x85\x46\x15\x9a\x43\xae\x8d\x21\x0c\x02\xad\x06\x78\x6a\xcd\x34\x6d\xb4\x40\x42\x03\x3f\x12\x91\x42\x5d\x92\x40\x99\xf2\x25\x21\xe3\x56\x0b\x16\x34\xbc\x97\x99\x36\xe9\xd2\x12\x2f\xab\x8c\xff\x5c\xf9\x73\x4e\x5a\x6b\xf3\x21\xd9\x59\xa3\x79\x52\x58\x48\x74\x8a\x13\x0d\x8a\x06\x5d\xc9\xbe\xd4\x9e\xd0\xb0\xbd\x26\x1a\xc3\x45\x18\x8a\xbc\x36\xb2\xd8\x92\x90\x42\x4c\xc1\xa4\xe9\x30\x8c\x70\x06\x6b\x24\x31\xbc\x29\x60\x99\x35\x7f\x74\xeb\xec\x99\xd9\xbe\xca\x68\x28\xfa\x14\xcd\xbc\xf9\xb3\xb9\x23\x43\x90\x27\xd1\xa4\xde\x9d\x59\x54\x5a\x44\x90\x99\x81\x85\x46\x91\xdb\x10\x46\xf1\x0c\x83\x1b\xf6\x73\x5a\xac\xd1\x8f\x82\x05\x2d\xcd\x9c\x26\x40\x71\xe2\x8c\x38\xbd\x5f\x40\x90\xa9\xb6\xc6\x1b\x0a\xbd\xbf\xe3\xdb\x83\x05\xe4\xfd\x20\xf9\xec\xc2\xf1\x3e\xbb\xfd\x05\xe3\x03\xf0\xe1\xc1\x42\xf1\x3e\xbf\x27\x14\x88\xbd\xfa\x7d\xb1\xc2\x70\x4f\xb5\x5d\x41\x48\x8b\xaf\x5f\x0f\xee\x01\x3a\x7f\x95\xaa\x00\xaf\xd4\x46\x65\xde\x43\xb6\x76\xba\xc6\x35\x25\x35\x1e\xb4\x62\xf6\x87\xbd\x89\xbe\x73\x45\xa0\x85\x73\x5e\x97\xeb\x93\x5b\xc6\x05\x39\xbd\x65\x7a\xae\x99\x97\xd9\xb8\xcb\xd5\x83\x73\xe1\x03\xef\x50\x9d\x9c\x55\x26\x1b\x3f\x53\x1b\x1b\xbc\x5d\x4d\xc8\x74\xf4\x09\x72\xd8\x75\x80\xf1\xb1\x95\x27\x49\x75\x0c\xa2\x02\x90\xf9\xce\x59\xf5\x28\x74\x4c\x67\x6c\x9b\xdb\xf8\x9e\xc4\xf7\xb3\x3b\xb5\xea\xe8\xed\x2a\xc3\xf5\xe3\xf1\xec\xde\xe7\xf2\xcf\xce\xf1\x7d\x4c\xf7\x67\xfa\x5e\x08\xf1\xc1\x7c\xdf\xc7\xfb\x09\x59\xff\x80\xde\x5f\x2c\xf7\x0f\x28\xbb\xab\x00\x49\xf2\xf5\x2b\xc0\x07\x40\xed\x5f\x21\xff\x89\xfb\x5d\x8e\x93\xab\xe1\x2c\xcb\x41\xf2\xf8\x53\x73\x9e\x1c\x6e\x6a\x9b\x28\x6a\xe5\xcd\x39\xc8\x10\xce\x9a\xeb\x15\x81\xdf\xbb\xdc\x0e\xdd\x36\xbb\x9f\x93\x5c\x01\x81\x11\xb8\x2e\x61\x37\xae\x43\x8f\xe7\xe4\x7d\x27\x7c\x76\x46\xde\x67\xb9\x3f\x1f\xf7\x00\xf5\x0f\x66\xe3\x7d\xbe\x4f\xc8\xc5\x83\xfa\x7e\xb1\x4c\xdc\xab\xa6\xe5\x61\x11\x7f\xfd\x34\xdc\xff\xff\x20\x4f\xcd\x46\x2e\xc3\xf3\x58\xa3\x9f\xae\x05\xe8\x71\x0b\x24\x72\x79\xed\x34\x1b\x98\x6f\x01\xff\xc8\x38\xf6\x17\xa9\xd2\xd2\xc5\x98\x93\x99\x4b\x01\x9a\x62\x03\x43\x98\x21\xc3\xdc\x69\x73\x38\x59\x19\x2e\xfb\x06\x70\x39\x03\x1d\x99\x75\x99\x9b\x59\xe3\x72\xcc\xac\xcd\xe5\x9b\x8b\x2c\xc0\x40\x6c\xc0\x1a\xf3\x6f\x90\xc5\xe9\x85\xbf\xab\xf5\x3d\xb5\x4c\x62\x2f\x97\x74\x66\x69\xf6\xd3\x0d\x01\x0b\x00\x31\x00\x52\xb8\xb0\x03\x16\x71\x79\x4e\x0d\x2c\x00\x7c\x09\xdd\x32\xd3\xb9\x3d\x37\x70\x32\x00\x65\x00\x39\x99\x82\x78\x85\x81\x39\xb5\xd9\xc3\x1b\xb0\x0a\xa0\x1a\x1b\x40\x53\x94\xba\x06\x30\x04\xaf\xaa\xb5\x3b\x42\xa3\xa0\x5b\x6e\x15\x00\x70\x00\xc0\x0b\xc0\x04\xb9\x01\x11\xd0\x1d\x20\x36\xd8\xa1\x54\xe0\xb1\xb6\xca\x02\x00\x48\x57\x77\xb5\x76\x7c\x40\x1c\xaa\x27\xa0\x14\xfb\x59\x07\x3c\x05\x64\x00\x2c\x40\xd7\x00\x3e\x65\x0a\x6c\xa1\xf3\xdc\x40\x27\x78\xe3\xc3\xa6\xd2\x29\x82\xc9\xa2\xb0\x89\x03\xdf\x00\x20\xa1\x4b\x00\x53\xb1\x05\x3e\x28\xd5\xd7\xe8\x1d\x6c\x54\x2b\xf8\x1b\x64\x91\xef\x33\x03\xb9\x00\x2e\x00\x4b\x90\x3d\x35\x50\x97\x38\xc2\x56\x00\x63\x54\x5d\x40\x4a\xc0\x53\xe8\x01\xae\x00\x87\x6a\x03\x3b\x91\x8f\x7d\x9c\x99\x9a\x2c\x99\xc5\x67\x00\x5d\x1b\x3d\x97\x29\xae\x33\xe0\x1b\x40\x16\x5a\x26\x1a\xec\x4c\x3c\x31\x49\xd5\x06\x24\xe2\x0f\xc0\x3c\xc0\x16\xe4\x4e\xed\x6e\x83\x5c\x00\x7b\x80\x4b\x8d\xf1\x72\x06\x4c\x05\x70\xac\x52\x10\x89\x18\x63\x1f\x40\x51\x65\x20\x17\xdd\x07\x7d\x00\xba\xd9\x1f\x40\x3c\xa7\xb1\x8a\xaf\xb0\x03\x7b\x2a\xab\xfc\xc4\x58\x00\x8a\x66\x7a\xf7\x44\x26\x57\x19\xf0\xed\x34\x4f\x39\x3b\x31\x79\xb8\x77\x11\x23\xb1\x81\x61\x80\x73\xd8\x26\x80\xec\x06\x1e\x03\x2e\x71\x16\xb2\x38\x03\xf4\x5a\xbb\xeb\x66\x99\xfa\x0e\xbf\x05\x10\xb5\x51\xff\x32\x41\xee\xf4\x09\xf9\xd0\x69\xa7\x0a\x31\xe8\xd4\xee\xe4\x06\x20\x1c\xa0\x16\xf6\x23\xb7\x82\x6d\xbd\xc6\x10\x76\x23\x27\xa0\x05\x44\xed\x0c\x78\xfd\xb0\xb3\x91\xaf\xf8\x3b\xe8\xd9\x6a\xce\x86\x7b\xe7\x83\x9d\xed\x60\x5d\xfb\xec\x06\x77\x90\xf3\xfe\x3e\x77\xf8\x3f\x9a\x1f\x6c\x77\x07\x4f\x79\x42\xd7\x7b\xcc\x16\xf7\x9a\xdf\xff\x02\x00\x00\xff\xff\x3c\x2b\x49\x80\x00\x20\x00\x00") func dbMigrationsAssetsGoBytes() ([]byte, error) { return bindataRead( diff --git a/db/refresh.go b/db/refresh.go index 9e2ef23c..317fb76b 100644 --- a/db/refresh.go +++ b/db/refresh.go @@ -1,6 +1,7 @@ package db import ( + "encoding/base64" "errors" "fmt" "strconv" @@ -31,7 +32,7 @@ type refreshTokenRepo struct { type refreshTokenModel struct { ID int64 `db:"id"` - PayloadHash string `db:"payload_hash"` + PayloadHash []byte `db:"payload_hash"` // TODO(yifan): Use some sort of foreign key to manage database level // data integrity. UserID string `db:"user_id"` @@ -39,25 +40,29 @@ type refreshTokenModel struct { } // buildToken combines the token ID and token payload to create a new token. -func buildToken(tokenID int64, tokenPayload string) string { - return fmt.Sprintf("%d%s%s", tokenID, refresh.TokenDelimer, tokenPayload) +func buildToken(tokenID int64, tokenPayload []byte) string { + return fmt.Sprintf("%d%s%s", tokenID, refresh.TokenDelimer, base64.URLEncoding.EncodeToString(tokenPayload)) } // parseToken parses a token and returns the token ID and token payload. -func parseToken(token string) (int64, string, error) { +func parseToken(token string) (int64, []byte, error) { parts := strings.SplitN(token, refresh.TokenDelimer, 2) if len(parts) != 2 { - return -1, "", refresh.ErrorInvalidToken + return -1, nil, refresh.ErrorInvalidToken } id, err := strconv.ParseInt(parts[0], 0, 64) if err != nil { - return -1, "", refresh.ErrorInvalidToken + return -1, nil, refresh.ErrorInvalidToken } - return id, parts[1], nil + tokenPayload, err := base64.URLEncoding.DecodeString(parts[1]) + if err != nil { + return -1, nil, refresh.ErrorInvalidToken + } + return id, tokenPayload, nil } -func checkTokenPayload(payloadHash, payload string) error { - if err := bcrypt.CompareHashAndPassword([]byte(payloadHash), []byte(payload)); err != nil { +func checkTokenPayload(payloadHash, payload []byte) error { + if err := bcrypt.CompareHashAndPassword(payloadHash, payload); err != nil { switch err { case bcrypt.ErrMismatchedHashAndPassword: return refresh.ErrorInvalidToken @@ -89,13 +94,13 @@ func (r *refreshTokenRepo) Create(userID, clientID string) (string, error) { return "", err } - payloadHash, err := bcrypt.GenerateFromPassword([]byte(tokenPayload), bcrypt.DefaultCost) + payloadHash, err := bcrypt.GenerateFromPassword(tokenPayload, bcrypt.DefaultCost) if err != nil { return "", err } record := &refreshTokenModel{ - PayloadHash: string(payloadHash), + PayloadHash: payloadHash, UserID: userID, ClientID: clientID, } @@ -109,6 +114,7 @@ func (r *refreshTokenRepo) Create(userID, clientID string) (string, error) { func (r *refreshTokenRepo) Verify(clientID, token string) (string, error) { tokenID, tokenPayload, err := parseToken(token) + if err != nil { return "", err } diff --git a/functional/db_test.go b/functional/db_test.go index 1177fa4e..1b99a8c3 100644 --- a/functional/db_test.go +++ b/functional/db_test.go @@ -1,6 +1,7 @@ package functional import ( + "encoding/base64" "fmt" "net/url" "os" @@ -342,6 +343,12 @@ func TestDBClientIdentityAll(t *testing.T) { } } +// buildRefreshToken combines the token ID and token payload to create a new token. +// used in the tests to created a refresh token. +func buildRefreshToken(tokenID int64, tokenPayload []byte) string { + return fmt.Sprintf("%d%s%s", tokenID, refresh.TokenDelimer, base64.URLEncoding.EncodeToString(tokenPayload)) +} + func TestDBRefreshRepoCreate(t *testing.T) { r := db.NewRefreshTokenRepo(connect(t)) @@ -383,6 +390,13 @@ func TestDBRefreshRepoVerify(t *testing.T) { t.Fatalf("Unexpected error: %v", err) } + badTokenPayload, err := refresh.DefaultRefreshTokenGenerator() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + tokenWithBadID := "404" + token[1:] + tokenWithBadPayload := buildRefreshToken(1, badTokenPayload) + tests := []struct { token string creds oidc.ClientCredentials @@ -390,7 +404,39 @@ func TestDBRefreshRepoVerify(t *testing.T) { expected string }{ { - "invalid-token-foo", + "invalid-token-format", + oidc.ClientCredentials{ID: "client-foo", Secret: "secret-foo"}, + refresh.ErrorInvalidToken, + "", + }, + { + "b/invalid-base64-encoded-format", + oidc.ClientCredentials{ID: "client-foo", Secret: "secret-foo"}, + refresh.ErrorInvalidToken, + "", + }, + { + "1/invalid-base64-encoded-format", + oidc.ClientCredentials{ID: "client-foo", Secret: "secret-foo"}, + refresh.ErrorInvalidToken, + "", + }, + { + token + "corrupted-token-payload", + oidc.ClientCredentials{ID: "client-foo", Secret: "secret-foo"}, + refresh.ErrorInvalidToken, + "", + }, + { + // The token's ID content is invalid. + tokenWithBadID, + oidc.ClientCredentials{ID: "client-foo", Secret: "secret-foo"}, + refresh.ErrorInvalidToken, + "", + }, + { + // The token's payload content is invalid. + tokenWithBadPayload, oidc.ClientCredentials{ID: "client-foo", Secret: "secret-foo"}, refresh.ErrorInvalidToken, "", @@ -428,13 +474,42 @@ func TestDBRefreshRepoRevoke(t *testing.T) { t.Fatalf("Unexpected error: %v", err) } + badTokenPayload, err := refresh.DefaultRefreshTokenGenerator() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + tokenWithBadID := "404" + token[1:] + tokenWithBadPayload := buildRefreshToken(1, badTokenPayload) + tests := []struct { token string userID string err error }{ { - "invalid-token-foo", + "invalid-token-format", + "user-foo", + refresh.ErrorInvalidToken, + }, + { + "1/invalid-base64-encoded-format", + "user-foo", + refresh.ErrorInvalidToken, + }, + { + token + "corrupted-token-payload", + "user-foo", + refresh.ErrorInvalidToken, + }, + { + // The token's ID is invalid. + tokenWithBadID, + "user-foo", + refresh.ErrorInvalidToken, + }, + { + // The token's payload is invalid. + tokenWithBadPayload, "user-foo", refresh.ErrorInvalidToken, }, diff --git a/refresh/refreshtest/repo.go b/refresh/refreshtest/repo.go index 8026a7c6..e93a852e 100644 --- a/refresh/refreshtest/repo.go +++ b/refresh/refreshtest/repo.go @@ -10,9 +10,9 @@ import ( // The tokens are in the form { refresh-1, refresh-2 ... refresh-n}. func NewTestRefreshTokenRepo() (refresh.RefreshTokenRepo, error) { var tokenIdx int - tokenGenerator := func() (string, error) { + tokenGenerator := func() ([]byte, error) { tokenIdx++ - return fmt.Sprintf("refresh-%d", tokenIdx), nil + return []byte(fmt.Sprintf("refresh-%d", tokenIdx)), nil } return refresh.NewRefreshTokenRepoWithTokenGenerator(tokenGenerator), nil } diff --git a/refresh/repo.go b/refresh/repo.go index df8d5482..d2fd5a05 100644 --- a/refresh/repo.go +++ b/refresh/repo.go @@ -1,8 +1,8 @@ package refresh import ( + "bytes" "crypto/rand" - "encoding/base64" "errors" "fmt" "strconv" @@ -17,25 +17,26 @@ const ( var ( ErrorInvalidUserID = errors.New("invalid user ID") ErrorInvalidClientID = errors.New("invalid client ID") - ErrorInvalidToken = errors.New("invalid token") + + ErrorInvalidToken = errors.New("invalid token") ) -type RefreshTokenGenerator func() (string, error) +type RefreshTokenGenerator func() ([]byte, error) -func (g RefreshTokenGenerator) Generate() (string, error) { +func (g RefreshTokenGenerator) Generate() ([]byte, error) { return g() } -func DefaultRefreshTokenGenerator() (string, error) { +func DefaultRefreshTokenGenerator() ([]byte, error) { // TODO(yifan) Remove this duplicated token generate function. b := make([]byte, DefaultRefreshTokenPayloadLength) n, err := rand.Read(b) if err != nil { - return "", err + return nil, err } else if n != DefaultRefreshTokenPayloadLength { - return "", errors.New("unable to read enough random bytes") + return nil, errors.New("unable to read enough random bytes") } - return base64.URLEncoding.EncodeToString(b), nil + return b, nil } type RefreshTokenRepo interface { @@ -52,7 +53,7 @@ type RefreshTokenRepo interface { } type refreshToken struct { - payload string + payload []byte userID string clientID string } @@ -63,21 +64,21 @@ type memRefreshTokenRepo struct { } // buildToken combines the token ID and token payload to create a new token. -func buildToken(tokenID int, tokenPayload string) string { +func buildToken(tokenID int, tokenPayload []byte) string { return fmt.Sprintf("%d%s%s", tokenID, TokenDelimer, tokenPayload) } // parseToken parses a token and returns the token ID and token payload. -func parseToken(token string) (int, string, error) { +func parseToken(token string) (int, []byte, error) { parts := strings.SplitN(token, TokenDelimer, 2) if len(parts) != 2 { - return -1, "", ErrorInvalidToken + return -1, nil, ErrorInvalidToken } id, err := strconv.Atoi(parts[0]) if err != nil { - return -1, "", ErrorInvalidToken + return -1, nil, ErrorInvalidToken } - return id, parts[1], nil + return id, []byte(parts[1]), nil } // NewRefreshTokenRepo returns an in-memory RefreshTokenRepo useful for development. @@ -131,7 +132,7 @@ func (r *memRefreshTokenRepo) Verify(clientID, token string) (string, error) { return "", ErrorInvalidToken } - if record.payload != tokenPayload { + if !bytes.Equal(record.payload, tokenPayload) { return "", ErrorInvalidToken } @@ -153,7 +154,7 @@ func (r *memRefreshTokenRepo) Revoke(userID, token string) error { return ErrorInvalidToken } - if record.payload != tokenPayload { + if !bytes.Equal(record.payload, tokenPayload) { return ErrorInvalidToken } diff --git a/server/server_test.go b/server/server_test.go index 49e36846..c1ca06ce 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -397,7 +397,7 @@ func TestServerTokenFail(t *testing.T) { signer jose.Signer argCC oidc.ClientCredentials argKey string - err string + err error scope []string refreshToken string }{ @@ -423,7 +423,7 @@ func TestServerTokenFail(t *testing.T) { signer: signerFixture, argCC: ccFixture, argKey: "foo", - err: oauth2.ErrorInvalidGrant, + err: oauth2.NewError(oauth2.ErrorInvalidGrant), scope: []string{"openid", "offline_access"}, }, @@ -432,7 +432,7 @@ func TestServerTokenFail(t *testing.T) { signer: signerFixture, argCC: oidc.ClientCredentials{ID: "YYY"}, argKey: keyFixture, - err: oauth2.ErrorInvalidClient, + err: oauth2.NewError(oauth2.ErrorInvalidClient), scope: []string{"openid", "offline_access"}, }, @@ -441,7 +441,7 @@ func TestServerTokenFail(t *testing.T) { signer: &StaticSigner{sig: nil, err: errors.New("fail")}, argCC: ccFixture, argKey: keyFixture, - err: oauth2.ErrorServerError, + err: oauth2.NewError(oauth2.ErrorServerError), scope: []string{"openid", "offline_access"}, }, } @@ -502,18 +502,14 @@ func TestServerTokenFail(t *testing.T) { t.Fatalf("case %d: expect refresh token %q, got %q", i, tt.refreshToken, token) panic("") } - if tt.err == "" { - if err != nil { - t.Errorf("case %d: got non-nil error: %v", i, err) - } else if jwt == nil { - t.Errorf("case %d: got nil JWT", i) - } - } else { - if err.Error() != tt.err { - t.Errorf("case %d: want err %q, got %q", i, tt.err, err.Error()) - } else if jwt != nil { - t.Errorf("case %d: got non-nil JWT", i) - } + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("case %d: expect %v, got %v", i, tt.err, err) + } + if err == nil && jwt == nil { + t.Errorf("case %d: got nil JWT", i) + } + if err != nil && jwt != nil { + t.Errorf("case %d: got non-nil JWT %v", i, jwt) } } } @@ -537,7 +533,7 @@ func TestServerRefreshToken(t *testing.T) { clientID string // The client that associates with the token. creds oidc.ClientCredentials signer jose.Signer - err string + err error }{ // Everything is good. { @@ -545,7 +541,7 @@ func TestServerRefreshToken(t *testing.T) { "XXX", credXXX, signerFixture, - "", + nil, }, // Invalid refresh token(malformatted). { @@ -553,15 +549,23 @@ func TestServerRefreshToken(t *testing.T) { "XXX", credXXX, signerFixture, - oauth2.ErrorInvalidRequest, + oauth2.NewError(oauth2.ErrorInvalidRequest), }, - // Invalid refresh token. + // Invalid refresh token(invalid payload content). { - "0/refresh-1", + "0/refresh-2", "XXX", credXXX, signerFixture, - oauth2.ErrorInvalidRequest, + oauth2.NewError(oauth2.ErrorInvalidRequest), + }, + // Invalid refresh token(invalid ID content). + { + "1/refresh-2", + "XXX", + credXXX, + signerFixture, + oauth2.NewError(oauth2.ErrorInvalidRequest), }, // Invalid client(client is not associated with the token). { @@ -569,7 +573,7 @@ func TestServerRefreshToken(t *testing.T) { "XXX", credYYY, signerFixture, - oauth2.ErrorInvalidClient, + oauth2.NewError(oauth2.ErrorInvalidClient), }, // Invalid client(no client ID). { @@ -577,7 +581,7 @@ func TestServerRefreshToken(t *testing.T) { "XXX", oidc.ClientCredentials{ID: "", Secret: "aaa"}, signerFixture, - oauth2.ErrorInvalidClient, + oauth2.NewError(oauth2.ErrorInvalidClient), }, // Invalid client(no such client). { @@ -585,7 +589,7 @@ func TestServerRefreshToken(t *testing.T) { "XXX", oidc.ClientCredentials{ID: "AAA", Secret: "aaa"}, signerFixture, - oauth2.ErrorInvalidClient, + oauth2.NewError(oauth2.ErrorInvalidClient), }, // Invalid client(no secrets). { @@ -593,7 +597,7 @@ func TestServerRefreshToken(t *testing.T) { "XXX", oidc.ClientCredentials{ID: "XXX"}, signerFixture, - oauth2.ErrorInvalidClient, + oauth2.NewError(oauth2.ErrorInvalidClient), }, // Invalid client(invalid secret). { @@ -601,7 +605,7 @@ func TestServerRefreshToken(t *testing.T) { "XXX", oidc.ClientCredentials{ID: "XXX", Secret: "bad-secret"}, signerFixture, - oauth2.ErrorInvalidClient, + oauth2.NewError(oauth2.ErrorInvalidClient), }, // Signing operation fails. { @@ -609,7 +613,7 @@ func TestServerRefreshToken(t *testing.T) { "XXX", credXXX, &StaticSigner{sig: nil, err: errors.New("fail")}, - oauth2.ErrorServerError, + oauth2.NewError(oauth2.ErrorServerError), }, } @@ -646,10 +650,8 @@ func TestServerRefreshToken(t *testing.T) { } jwt, err := srv.RefreshToken(tt.creds, tt.token) - if err != nil { - if err.Error() != tt.err { - t.Errorf("Case %d: expect: %v, got: %v", i, tt.err, err) - } + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("Case %d: expect: %v, got: %v", i, tt.err, err) } if jwt != nil { @@ -715,7 +717,7 @@ func TestServerRefreshToken(t *testing.T) { srv.UserRepo = userRepo _, err = srv.RefreshToken(credXXX, "0/refresh-1") - if err == nil || err.Error() != oauth2.ErrorServerError { - t.Errorf("Expect: %v, got: %v", oauth2.ErrorServerError, err) + if !reflect.DeepEqual(err, oauth2.NewError(oauth2.ErrorServerError)) { + t.Errorf("Expect: %v, got: %v", oauth2.NewError(oauth2.ErrorServerError), err) } }