You've already forked golang-saas-starter-kit
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:
1
example-project/cmd/schema/.gitignore
vendored
1
example-project/cmd/schema/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
schema
|
schema
|
||||||
|
local.env
|
||||||
|
1
example-project/cmd/web-api/.gitignore
vendored
Normal file
1
example-project/cmd/web-api/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
local.env
|
@ -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
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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() {
|
||||||
|
|
||||||
|
1
example-project/cmd/web-app/.gitignore
vendored
Normal file
1
example-project/cmd/web-app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
local.env
|
@ -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
|
||||||
|
@ -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=
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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:"-"`
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user