1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-17 00:17:59 +02:00

completed API from signup to auth token with swagger UI.

This commit is contained in:
Lee Brown
2019-06-25 06:25:55 -08:00
parent 2fbda74a73
commit 8328cf525b
15 changed files with 443 additions and 26 deletions

View File

@ -1 +1,2 @@
schema schema
local.env

View File

@ -0,0 +1 @@
local.env

View File

@ -25,6 +25,129 @@ To build using the docker file, need to be in the project root directory. `Docke
docker build -f cmd/web-api/Dockerfile -t saas-web-api . docker build -f cmd/web-api/Dockerfile -t saas-web-api .
``` ```
## Getting Started
1. Ensure postgres is running.
Navigate to the project root where `docker-compose.yaml` exists. There is only
one `docker-compose.yaml` file that is shared between all services.
*Start Postgres.*
```bash
docker-compose up -d postgres
```
2. Set env variables.
*Copy the sample file to make your own copy.*
```bash
cp sample.env local.env
```
*Make any changes to your copy of the file if necessary and then add them to your env.
```bash
source local.env
```
3. Start the web-api service.
*Invoke main.go directly or use `go build .`*
```bash
go run main.go
```
4. Open the Swagger UI.
Navigate your browser to [http://localhost:3000/swagger](http://localhost:3000/swagger).
5. Signup a new account.
Find the `signup` endpoint in the Swagger UI.
Click `Try it out`. Example data has been prepopulated
to generate a valid POST request.
```json
{
"account": {
"address1": "221 Tatitlek Ave",
"address2": "Box #1832",
"city": "Valdez",
"country": "USA",
"name": "Company 895ff280-5ed9-4b09-b7bc-86ab0f0951d4",
"region": "AK",
"timezone": "America/Anchorage",
"zipcode": "99686"
},
"user": {
"email": "90873f61-663e-43d1-8f0c-00415e73f650@example.com",
"name": "Gabi May",
"password": "SecretString",
"password_confirm": "SecretString"
}
}
```
**Note the user email and password from the request to be used in the following steps.**
Click `Execute` and a response with status code 200 should have been returned.
```json
{
"account": {
"id": "baae6e0d-29ae-456f-9648-44c1e90ca8af",
"name": "Company 895ff280-5ed9-4b09-b7bc-86ab0f0951d4",
"address1": "221 Tatitlek Ave",
"address2": "Box #1832",
"city": "Valdez",
"region": "AK",
"country": "USA",
"zipcode": "99686",
"status": "active",
"timezone": "America/Anchorage",
"signup_user_id": {
"String": "bfdc5ca9-872c-4417-8030-e1b4962a107c",
"Valid": true
},
"billing_user_id": {
"String": "bfdc5ca9-872c-4417-8030-e1b4962a107c",
"Valid": true
},
"created_at": "2019-06-25T11:00:53.284Z",
"updated_at": "2019-06-25T11:00:53.284Z"
},
"user": {
"id": "bfdc5ca9-872c-4417-8030-e1b4962a107c",
"name": "Gabi May",
"email": "90873f61-663e-43d1-8f0c-00415e73f650@example.com",
"timezone": "America/Anchorage",
"created_at": "2019-06-25T11:00:53.284Z",
"updated_at": "2019-06-25T11:00:53.284Z"
}
}
```
6. Generate an Auth Token
An auth token is required for all other requests.
Near the top of the Swagger UI locate the button `Authorize` and click it.
Find the section `OAuth2Password (OAuth2, password)`
Enter the user email and password.
Change the type to `basic auth`
Click the button `Authorize` to generate a token that will be used by the Swagger UI for all future requests.
7. Test Auth Token
Now that the Swagger UI is authorized, try running endpoint using the oauth token.
Find the endpoint GET `/accounts/{id}` endpoint in the Swagger UI. This endpoint should return the account by ID.
Click `Try it out` and enter the account ID from generated from signup (step 5).
Click `Execute`. The response should be of an Account.
## API Documentation ## API Documentation

View File

@ -1,6 +1,6 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at // This file was generated by swaggo/swag at
// 2019-06-25 02:19:21.144417 -0800 AKDT m=+51.040366621 // 2019-06-25 06:15:54.005963 -0800 AKDT m=+73.603716546
package docs package docs
@ -33,6 +33,11 @@ var doc = `{
"paths": { "paths": {
"/accounts/{id}": { "/accounts/{id}": {
"get": { "get": {
"security": [
{
"OAuth2Password": []
}
],
"description": "get string by ID", "description": "get string by ID",
"consumes": [ "consumes": [
"application/json" "application/json"
@ -44,10 +49,9 @@ var doc = `{
"account" "account"
], ],
"summary": "Read returns the specified account from the system.", "summary": "Read returns the specified account from the system.",
"operationId": "get-string-by-int",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "string",
"description": "Account ID", "description": "Account ID",
"name": "id", "name": "id",
"in": "path", "in": "path",
@ -92,6 +96,62 @@ var doc = `{
} }
} }
}, },
"/oauth/token": {
"post": {
"security": [
{
"BasicAuth": []
}
],
"description": "Token generates an oauth2 accessToken using Basic Auth with a user's email and password.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Token handles a request to authenticate a user.",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"$ref": "#/definitions/user.Token"
},
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
}
}
}
},
"/signup": { "/signup": {
"post": { "post": {
"description": "Signup creates a new account and user in the system.", "description": "Signup creates a new account and user in the system.",
@ -150,6 +210,11 @@ var doc = `{
}, },
"/users/{id}": { "/users/{id}": {
"get": { "get": {
"security": [
{
"OAuth2Password": []
}
],
"description": "get string by ID", "description": "get string by ID",
"consumes": [ "consumes": [
"application/json" "application/json"
@ -161,14 +226,20 @@ var doc = `{
"user" "user"
], ],
"summary": "Read returns the specified user from the system.", "summary": "Read returns the specified user from the system.",
"operationId": "get-string-by-int",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "string",
"description": "User ID", "description": "User ID",
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true "required": true
},
{
"type": "string",
"description": "Authentication header",
"name": "Authorization",
"in": "header",
"required": true
} }
], ],
"responses": { "responses": {
@ -360,6 +431,20 @@ var doc = `{
} }
} }
}, },
"user.Token": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"expiry": {
"type": "string"
},
"token_type": {
"type": "string"
}
}
},
"user.User": { "user.User": {
"type": "object", "type": "object",
"required": [ "required": [
@ -412,10 +497,18 @@ var doc = `{
} }
}, },
"securityDefinitions": { "securityDefinitions": {
"BasicAuth": {
"type": "basic"
},
"OAuth2Password": { "OAuth2Password": {
"type": "oauth2", "type": "oauth2",
"flow": "password", "flow": "password",
"tokenUrl": "/v1/oauth/token" "tokenUrl": "/v1/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
"read": " Grants read access",
"write": " Grants write access"
}
} }
} }
}` }`

View File

@ -20,6 +20,11 @@
"paths": { "paths": {
"/accounts/{id}": { "/accounts/{id}": {
"get": { "get": {
"security": [
{
"OAuth2Password": []
}
],
"description": "get string by ID", "description": "get string by ID",
"consumes": [ "consumes": [
"application/json" "application/json"
@ -31,10 +36,9 @@
"account" "account"
], ],
"summary": "Read returns the specified account from the system.", "summary": "Read returns the specified account from the system.",
"operationId": "get-string-by-int",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "string",
"description": "Account ID", "description": "Account ID",
"name": "id", "name": "id",
"in": "path", "in": "path",
@ -79,6 +83,62 @@
} }
} }
}, },
"/oauth/token": {
"post": {
"security": [
{
"BasicAuth": []
}
],
"description": "Token generates an oauth2 accessToken using Basic Auth with a user's email and password.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Token handles a request to authenticate a user.",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"$ref": "#/definitions/user.Token"
},
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
}
}
}
},
"/signup": { "/signup": {
"post": { "post": {
"description": "Signup creates a new account and user in the system.", "description": "Signup creates a new account and user in the system.",
@ -137,6 +197,11 @@
}, },
"/users/{id}": { "/users/{id}": {
"get": { "get": {
"security": [
{
"OAuth2Password": []
}
],
"description": "get string by ID", "description": "get string by ID",
"consumes": [ "consumes": [
"application/json" "application/json"
@ -148,14 +213,20 @@
"user" "user"
], ],
"summary": "Read returns the specified user from the system.", "summary": "Read returns the specified user from the system.",
"operationId": "get-string-by-int",
"parameters": [ "parameters": [
{ {
"type": "integer", "type": "string",
"description": "User ID", "description": "User ID",
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true "required": true
},
{
"type": "string",
"description": "Authentication header",
"name": "Authorization",
"in": "header",
"required": true
} }
], ],
"responses": { "responses": {
@ -347,6 +418,20 @@
} }
} }
}, },
"user.Token": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"expiry": {
"type": "string"
},
"token_type": {
"type": "string"
}
}
},
"user.User": { "user.User": {
"type": "object", "type": "object",
"required": [ "required": [
@ -399,10 +484,18 @@
} }
}, },
"securityDefinitions": { "securityDefinitions": {
"BasicAuth": {
"type": "basic"
},
"OAuth2Password": { "OAuth2Password": {
"type": "oauth2", "type": "oauth2",
"flow": "password", "flow": "password",
"tokenUrl": "/v1/oauth/token" "tokenUrl": "/v1/oauth/token",
"scopes": {
"admin": " Grants read and write access to administrative information",
"read": " Grants read access",
"write": " Grants write access"
}
} }
} }
} }

View File

@ -108,6 +108,15 @@ definitions:
$ref: '#/definitions/user.User' $ref: '#/definitions/user.User'
type: object type: object
type: object type: object
user.Token:
properties:
access_token:
type: string
expiry:
type: string
token_type:
type: string
type: object
user.User: user.User:
properties: properties:
archived_at: archived_at:
@ -161,13 +170,12 @@ paths:
consumes: consumes:
- application/json - application/json
description: get string by ID description: get string by ID
operationId: get-string-by-int
parameters: parameters:
- description: Account ID - description: Account ID
in: path in: path
name: id name: id
required: true required: true
type: integer type: string
produces: produces:
- application/json - application/json
responses: responses:
@ -195,9 +203,49 @@ paths:
schema: schema:
$ref: '#/definitions/web.Error' $ref: '#/definitions/web.Error'
type: object type: object
security:
- OAuth2Password: []
summary: Read returns the specified account from the system. summary: Read returns the specified account from the system.
tags: tags:
- account - account
/oauth/token:
post:
consumes:
- application/json
description: Token generates an oauth2 accessToken using Basic Auth with a user's
email and password.
produces:
- application/json
responses:
"200":
description: OK
headers:
Token:
description: qwerty
type: string
schema:
$ref: '#/definitions/user.Token'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/web.Error'
type: object
"403":
description: Forbidden
schema:
$ref: '#/definitions/web.Error'
type: object
"404":
description: Not Found
schema:
$ref: '#/definitions/web.Error'
type: object
security:
- BasicAuth: []
summary: Token handles a request to authenticate a user.
tags:
- user
/signup: /signup:
post: post:
consumes: consumes:
@ -241,13 +289,17 @@ paths:
consumes: consumes:
- application/json - application/json
description: get string by ID description: get string by ID
operationId: get-string-by-int
parameters: parameters:
- description: User ID - description: User ID
in: path in: path
name: id name: id
required: true required: true
type: integer type: string
- description: Authentication header
in: header
name: Authorization
required: true
type: string
produces: produces:
- application/json - application/json
responses: responses:
@ -275,12 +327,20 @@ paths:
schema: schema:
$ref: '#/definitions/web.Error' $ref: '#/definitions/web.Error'
type: object type: object
security:
- OAuth2Password: []
summary: Read returns the specified user from the system. summary: Read returns the specified user from the system.
tags: tags:
- user - user
securityDefinitions: securityDefinitions:
BasicAuth:
type: basic
OAuth2Password: OAuth2Password:
flow: password flow: password
scopes:
admin: ' Grants read and write access to administrative information'
read: ' Grants read access'
write: ' Grants write access'
tokenUrl: /v1/oauth/token tokenUrl: /v1/oauth/token
type: oauth2 type: oauth2
swagger: "2.0" swagger: "2.0"

View File

@ -43,10 +43,10 @@ func (a *Account) Find(ctx context.Context, w http.ResponseWriter, r *http.Reque
// @Summary Read returns the specified account from the system. // @Summary Read returns the specified account from the system.
// @Description get string by ID // @Description get string by ID
// @Tags account // @Tags account
// @ID get-string-by-int
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Account ID" // @Security OAuth2Password
// @Param id path string true "Account ID"
// @Success 200 {object} account.Account // @Success 200 {object} account.Account
// @Header 200 {string} Token "qwerty" // @Header 200 {string} Token "qwerty"
// @Failure 400 {object} web.Error // @Failure 400 {object} web.Error

View File

@ -48,10 +48,10 @@ func (u *User) Find(ctx context.Context, w http.ResponseWriter, r *http.Request,
// @Summary Read returns the specified user from the system. // @Summary Read returns the specified user from the system.
// @Description get string by ID // @Description get string by ID
// @Tags user // @Tags user
// @ID get-string-by-int
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "User ID" // @Security OAuth2Password
// @Param id path string true "User ID"
// @Success 200 {object} user.User // @Success 200 {object} user.User
// @Header 200 {string} Token "qwerty" // @Header 200 {string} Token "qwerty"
// @Failure 400 {object} web.Error // @Failure 400 {object} web.Error
@ -268,8 +268,19 @@ func (u *User) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http
return web.RespondJson(ctx, w, tkn, http.StatusNoContent) return web.RespondJson(ctx, w, tkn, http.StatusNoContent)
} }
// Token handles a request to authenticate a user. It expects a request using // Token godoc
// Basic Auth with a user's email and password. It responds with a JWT. // @Summary Token handles a request to authenticate a user.
// @Description Token generates an oauth2 accessToken using Basic Auth with a user's email and password.
// @Tags user
// @Accept json
// @Produce json
// @Security BasicAuth
// @Success 200 {object} user.Token
// @Header 200 {string} Token "qwerty"
// @Failure 400 {object} web.Error
// @Failure 403 {object} web.Error
// @Failure 404 {object} web.Error
// @Router /oauth/token [post]
func (u *User) Token(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { func (u *User) Token(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
v, ok := ctx.Value(web.KeyValues).(*web.Values) v, ok := ctx.Value(web.KeyValues).(*web.Values)
if !ok { if !ok {

View File

@ -51,8 +51,13 @@ var service = "WEB_API"
// @license.name Apache 2.0 // @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html // @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @securityDefinitions.basic BasicAuth
// @securitydefinitions.oauth2.password OAuth2Password // @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl /v1/oauth/token // @tokenUrl /v1/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
func main() { func main() {

View File

@ -0,0 +1 @@
local.env

View File

@ -38,6 +38,7 @@ require (
github.com/urfave/cli v1.20.0 github.com/urfave/cli v1.20.0
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 // indirect golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 // indirect
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4 // indirect golang.org/x/tools v0.0.0-20190624222133-a101b041ded4 // indirect
google.golang.org/appengine v1.6.0 // indirect google.golang.org/appengine v1.6.0 // indirect

View File

@ -1,3 +1,4 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@ -130,12 +131,16 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225 h1:kNX+jCowfMYzvlSvJu5pQWEmy
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -154,6 +159,7 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4 h1:1mMox4TgefDwqluYCv677yNXwlfTkija4owZve/jr78= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4 h1:1mMox4TgefDwqluYCv677yNXwlfTkija4owZve/jr78=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/DataDog/dd-trace-go.v1 v1.15.0 h1:2LhklnAJsRSelbnBrrE5QuRleRDkmOh2JWxOtIX6yec= gopkg.in/DataDog/dd-trace-go.v1 v1.15.0 h1:2LhklnAJsRSelbnBrrE5QuRleRDkmOh2JWxOtIX6yec=

View File

@ -219,12 +219,22 @@ func generateToken(ctx context.Context, dbConn *sqlx.DB, tknGen TokenGenerator,
claims = auth.NewClaims(userID, accountID, accountIds, account.Roles, now, expires) claims = auth.NewClaims(userID, accountID, accountIds, account.Roles, now, expires)
// Generate a token for the user with the defined claims. // Generate a token for the user with the defined claims.
tkn, err := tknGen.GenerateToken(claims) tknStr, err := tknGen.GenerateToken(claims)
if err != nil { if err != nil {
return Token{}, errors.Wrap(err, "generating token") return Token{}, errors.Wrap(err, "generating token")
} }
return Token{Token: tkn, claims: claims}, nil tkn := Token{
AccessToken: tknStr,
TokenType: "Bearer",
claims: claims,
}
if expires.Seconds() > 0 {
tkn.Expiry = now.Add(expires)
}
return tkn, nil
} }
// mockTokenGenerator is used for testing that Authenticate calls its provided // mockTokenGenerator is used for testing that Authenticate calls its provided

View File

@ -105,7 +105,7 @@ func TestAuthenticate(t *testing.T) {
t.Logf("\t%s\tAuthenticate user ok.", tests.Success) t.Logf("\t%s\tAuthenticate user ok.", tests.Success)
// Ensure the token string was correctly generated. // Ensure the token string was correctly generated.
claims1, err := tknGen.ParseClaims(tkn1.Token) claims1, err := tknGen.ParseClaims(tkn1.AccessToken)
if err != nil { if err != nil {
t.Log("\t\tGot :", err) t.Log("\t\tGot :", err)
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed) t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)
@ -127,7 +127,7 @@ func TestAuthenticate(t *testing.T) {
t.Logf("\t%s\tSwitchAccount user ok.", tests.Success) t.Logf("\t%s\tSwitchAccount user ok.", tests.Success)
// Ensure the token string was correctly generated. // Ensure the token string was correctly generated.
claims2, err := tknGen.ParseClaims(tkn2.Token) claims2, err := tknGen.ParseClaims(tkn2.AccessToken)
if err != nil { if err != nil {
t.Log("\t\tGot :", err) t.Log("\t\tGot :", err)
t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed) t.Fatalf("\t%s\tParse claims from token failed.", tests.Failed)

View File

@ -67,6 +67,18 @@ type UserFindRequest struct {
// Token is the payload we deliver to users when they authenticate. // Token is the payload we deliver to users when they authenticate.
type Token struct { type Token struct {
Token string `json:"token" validate:"required"` // AccessToken is the token that authorizes and authenticates
// the requests.
AccessToken string `json:"access_token"`
// TokenType is the type of token.
// The Type method returns either this or "Bearer", the default.
TokenType string `json:"token_type,omitempty"`
// Expiry is the optional expiration time of the access token.
//
// If zero, TokenSource implementations will reuse the same
// token forever and RefreshToken or equivalent
// mechanisms for that TokenSource will not be used.
Expiry time.Time `json:"expiry,omitempty"`
// contains filtered or unexported fields
claims auth.Claims `json:"-"` claims auth.Claims `json:"-"`
} }