basic login service/state (#17)

This commit is contained in:
Michael Quigley 2022-08-02 13:23:31 -04:00
parent cf1abe7282
commit e699994ca5
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
26 changed files with 875 additions and 476 deletions

View File

@ -25,4 +25,4 @@ echo "...generating zrok client"
swagger generate client -P rest_model_zrok.Principal -f "$zrokSpec" -c rest_client_zrok -t "$zrokDir" -m "rest_model_zrok"
echo "...generating js client"
openapi -s specs/zrok.yml -o ui/api -l js
openapi -s specs/zrok.yml -o ui/src/api -l js

View File

@ -14,7 +14,7 @@ func createAccountHandler(params identity.CreateAccountParams) middleware.Respon
logrus.Infof("received account request for username '%v'", params.Body.Username)
if params.Body == nil || params.Body.Username == "" || params.Body.Password == "" {
logrus.Errorf("missing username or password")
return identity.NewCreateAccountBadRequest().WithPayload(rest_model_zrok.ErrorMessage("missing username or password"))
return identity.NewCreateAccountBadRequest().WithPayload("missing username or password")
}
token, err := generateApiToken()

View File

@ -22,9 +22,10 @@ func Run(cfg *Config) error {
api := operations.NewZrokAPI(swaggerSpec)
api.KeyAuth = ZrokAuthenticate
api.MetadataVersionHandler = metadata.VersionHandlerFunc(versionHandler)
api.IdentityCreateAccountHandler = identity.CreateAccountHandlerFunc(createAccountHandler)
api.IdentityEnableHandler = identity.EnableHandlerFunc(enableHandler)
api.IdentityLoginHandler = identity.LoginHandlerFunc(loginHandler)
api.MetadataVersionHandler = metadata.VersionHandlerFunc(versionHandler)
api.TunnelTunnelHandler = tunnel.TunnelHandlerFunc(tunnelHandler)
api.TunnelUntunnelHandler = tunnel.UntunnelHandlerFunc(untunnelHandler)

35
controller/login.go Normal file
View File

@ -0,0 +1,35 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/identity"
"github.com/sirupsen/logrus"
)
func loginHandler(params identity.LoginParams) middleware.Responder {
if params.Body == nil || params.Body.Email == "" || params.Body.Password == "" {
logrus.Errorf("missing email or password")
return identity.NewLoginUnauthorized()
}
logrus.Infof("received login request for email '%v'", params.Body.Email)
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return identity.NewLoginUnauthorized()
}
defer func() { _ = tx.Rollback() }()
a, err := str.FindAccountWithUsername(params.Body.Email, tx)
if err != nil {
logrus.Errorf("error finding account '%v': %v", params.Body.Email, err)
return identity.NewLoginUnauthorized()
}
if a.Password != hashPassword(params.Body.Password) {
logrus.Errorf("password mismatch for account '%v'", params.Body.Email)
return identity.NewLoginUnauthorized()
}
return identity.NewLoginOK().WithPayload(rest_model_zrok.LoginResponse(a.Token))
}

View File

@ -36,6 +36,14 @@ func (self *Store) GetAccount(id int, tx *sqlx.Tx) (*Account, error) {
return a, nil
}
func (self *Store) FindAccountWithUsername(username string, tx *sqlx.Tx) (*Account, error) {
a := &Account{}
if err := tx.QueryRowx("select * from accounts where username = ?", username).StructScan(a); err != nil {
return nil, errors.Wrap(err, "error selecting account by username")
}
return a, nil
}
func (self *Store) FindAccountWithToken(token string, tx *sqlx.Tx) (*Account, error) {
a := &Account{}
if err := tx.QueryRowx("select * from accounts where token = ?", token).StructScan(a); err != nil {

View File

@ -34,6 +34,8 @@ type ClientService interface {
Enable(params *EnableParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*EnableCreated, error)
Login(params *LoginParams, opts ...ClientOption) (*LoginOK, error)
SetTransport(transport runtime.ClientTransport)
}
@ -114,6 +116,44 @@ func (a *Client) Enable(params *EnableParams, authInfo runtime.ClientAuthInfoWri
panic(msg)
}
/*
Login login API
*/
func (a *Client) Login(params *LoginParams, opts ...ClientOption) (*LoginOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewLoginParams()
}
op := &runtime.ClientOperation{
ID: "login",
Method: "POST",
PathPattern: "/login",
ProducesMediaTypes: []string{"application/zrok.v1+json"},
ConsumesMediaTypes: []string{"application/zrok.v1+json"},
Schemes: []string{"http"},
Params: params,
Reader: &LoginReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*LoginOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for login: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport

View File

@ -0,0 +1,148 @@
// Code generated by go-swagger; DO NOT EDIT.
package identity
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
)
// NewLoginParams creates a new LoginParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewLoginParams() *LoginParams {
return &LoginParams{
timeout: cr.DefaultTimeout,
}
}
// NewLoginParamsWithTimeout creates a new LoginParams object
// with the ability to set a timeout on a request.
func NewLoginParamsWithTimeout(timeout time.Duration) *LoginParams {
return &LoginParams{
timeout: timeout,
}
}
// NewLoginParamsWithContext creates a new LoginParams object
// with the ability to set a context for a request.
func NewLoginParamsWithContext(ctx context.Context) *LoginParams {
return &LoginParams{
Context: ctx,
}
}
// NewLoginParamsWithHTTPClient creates a new LoginParams object
// with the ability to set a custom HTTPClient for a request.
func NewLoginParamsWithHTTPClient(client *http.Client) *LoginParams {
return &LoginParams{
HTTPClient: client,
}
}
/* LoginParams contains all the parameters to send to the API endpoint
for the login operation.
Typically these are written to a http.Request.
*/
type LoginParams struct {
// Body.
Body *rest_model_zrok.LoginRequest
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the login params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *LoginParams) WithDefaults() *LoginParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the login params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *LoginParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the login params
func (o *LoginParams) WithTimeout(timeout time.Duration) *LoginParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the login params
func (o *LoginParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the login params
func (o *LoginParams) WithContext(ctx context.Context) *LoginParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the login params
func (o *LoginParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the login params
func (o *LoginParams) WithHTTPClient(client *http.Client) *LoginParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the login params
func (o *LoginParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the login params
func (o *LoginParams) WithBody(body *rest_model_zrok.LoginRequest) *LoginParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the login params
func (o *LoginParams) SetBody(body *rest_model_zrok.LoginRequest) {
o.Body = body
}
// WriteToRequest writes these params to a swagger request
func (o *LoginParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if o.Body != nil {
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@ -0,0 +1,92 @@
// Code generated by go-swagger; DO NOT EDIT.
package identity
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
)
// LoginReader is a Reader for the Login structure.
type LoginReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *LoginReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewLoginOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
case 401:
result := NewLoginUnauthorized()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
// NewLoginOK creates a LoginOK with default headers values
func NewLoginOK() *LoginOK {
return &LoginOK{}
}
/* LoginOK describes a response with status code 200, with default header values.
login successful
*/
type LoginOK struct {
Payload rest_model_zrok.LoginResponse
}
func (o *LoginOK) Error() string {
return fmt.Sprintf("[POST /login][%d] loginOK %+v", 200, o.Payload)
}
func (o *LoginOK) GetPayload() rest_model_zrok.LoginResponse {
return o.Payload
}
func (o *LoginOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewLoginUnauthorized creates a LoginUnauthorized with default headers values
func NewLoginUnauthorized() *LoginUnauthorized {
return &LoginUnauthorized{}
}
/* LoginUnauthorized describes a response with status code 401, with default header values.
invalid login
*/
type LoginUnauthorized struct {
}
func (o *LoginUnauthorized) Error() string {
return fmt.Sprintf("[POST /login][%d] loginUnauthorized ", 401)
}
func (o *LoginUnauthorized) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
}

View File

@ -0,0 +1,53 @@
// Code generated by go-swagger; DO NOT EDIT.
package rest_model_zrok
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// LoginRequest login request
//
// swagger:model loginRequest
type LoginRequest struct {
// email
Email string `json:"email,omitempty"`
// password
Password string `json:"password,omitempty"`
}
// Validate validates this login request
func (m *LoginRequest) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this login request based on context it is used
func (m *LoginRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *LoginRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *LoginRequest) UnmarshalBinary(b []byte) error {
var res LoginRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@ -0,0 +1,27 @@
// Code generated by go-swagger; DO NOT EDIT.
package rest_model_zrok
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
)
// LoginResponse login response
//
// swagger:model loginResponse
type LoginResponse string
// Validate validates this login response
func (m LoginResponse) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this login response based on context it is used
func (m LoginResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}

View File

@ -105,6 +105,34 @@ func init() {
}
}
},
"/login": {
"post": {
"tags": [
"identity"
],
"operationId": "login",
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/loginRequest"
}
}
],
"responses": {
"200": {
"description": "login successful",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"401": {
"description": "invalid login"
}
}
}
},
"/tunnel": {
"post": {
"security": [
@ -237,6 +265,20 @@ func init() {
"errorMessage": {
"type": "string"
},
"loginRequest": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"loginResponse": {
"type": "string"
},
"principal": {
"type": "object",
"properties": {
@ -378,6 +420,34 @@ func init() {
}
}
},
"/login": {
"post": {
"tags": [
"identity"
],
"operationId": "login",
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/loginRequest"
}
}
],
"responses": {
"200": {
"description": "login successful",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"401": {
"description": "invalid login"
}
}
}
},
"/tunnel": {
"post": {
"security": [
@ -510,6 +580,20 @@ func init() {
"errorMessage": {
"type": "string"
},
"loginRequest": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"loginResponse": {
"type": "string"
},
"principal": {
"type": "object",
"properties": {

View File

@ -0,0 +1,56 @@
// Code generated by go-swagger; DO NOT EDIT.
package identity
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// LoginHandlerFunc turns a function with the right signature into a login handler
type LoginHandlerFunc func(LoginParams) middleware.Responder
// Handle executing the request and returning a response
func (fn LoginHandlerFunc) Handle(params LoginParams) middleware.Responder {
return fn(params)
}
// LoginHandler interface for that can handle valid login params
type LoginHandler interface {
Handle(LoginParams) middleware.Responder
}
// NewLogin creates a new http.Handler for the login operation
func NewLogin(ctx *middleware.Context, handler LoginHandler) *Login {
return &Login{Context: ctx, Handler: handler}
}
/* Login swagger:route POST /login identity login
Login login API
*/
type Login struct {
Context *middleware.Context
Handler LoginHandler
}
func (o *Login) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewLoginParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@ -0,0 +1,77 @@
// Code generated by go-swagger; DO NOT EDIT.
package identity
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/validate"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
)
// NewLoginParams creates a new LoginParams object
//
// There are no default values defined in the spec.
func NewLoginParams() LoginParams {
return LoginParams{}
}
// LoginParams contains all the bound params for the login operation
// typically these are obtained from a http.Request
//
// swagger:parameters login
type LoginParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
In: body
*/
Body *rest_model_zrok.LoginRequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewLoginParams() beforehand.
func (o *LoginParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body rest_model_zrok.LoginRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
res = append(res, errors.NewParseError("body", "body", "", err))
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background())
if err := body.ContextValidate(ctx, route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@ -0,0 +1,80 @@
// Code generated by go-swagger; DO NOT EDIT.
package identity
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
)
// LoginOKCode is the HTTP code returned for type LoginOK
const LoginOKCode int = 200
/*LoginOK login successful
swagger:response loginOK
*/
type LoginOK struct {
/*
In: Body
*/
Payload rest_model_zrok.LoginResponse `json:"body,omitempty"`
}
// NewLoginOK creates LoginOK with default headers values
func NewLoginOK() *LoginOK {
return &LoginOK{}
}
// WithPayload adds the payload to the login o k response
func (o *LoginOK) WithPayload(payload rest_model_zrok.LoginResponse) *LoginOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login o k response
func (o *LoginOK) SetPayload(payload rest_model_zrok.LoginResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
// LoginUnauthorizedCode is the HTTP code returned for type LoginUnauthorized
const LoginUnauthorizedCode int = 401
/*LoginUnauthorized invalid login
swagger:response loginUnauthorized
*/
type LoginUnauthorized struct {
}
// NewLoginUnauthorized creates LoginUnauthorized with default headers values
func NewLoginUnauthorized() *LoginUnauthorized {
return &LoginUnauthorized{}
}
// WriteResponse to the client
func (o *LoginUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(401)
}

View File

@ -0,0 +1,87 @@
// Code generated by go-swagger; DO NOT EDIT.
package identity
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// LoginURL generates an URL for the login operation
type LoginURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginURL) WithBasePath(bp string) *LoginURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *LoginURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/login"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *LoginURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *LoginURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *LoginURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on LoginURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on LoginURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *LoginURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@ -53,6 +53,9 @@ func NewZrokAPI(spec *loads.Document) *ZrokAPI {
IdentityEnableHandler: identity.EnableHandlerFunc(func(params identity.EnableParams, principal *rest_model_zrok.Principal) middleware.Responder {
return middleware.NotImplemented("operation identity.Enable has not yet been implemented")
}),
IdentityLoginHandler: identity.LoginHandlerFunc(func(params identity.LoginParams) middleware.Responder {
return middleware.NotImplemented("operation identity.Login has not yet been implemented")
}),
TunnelTunnelHandler: tunnel.TunnelHandlerFunc(func(params tunnel.TunnelParams, principal *rest_model_zrok.Principal) middleware.Responder {
return middleware.NotImplemented("operation tunnel.Tunnel has not yet been implemented")
}),
@ -116,6 +119,8 @@ type ZrokAPI struct {
IdentityCreateAccountHandler identity.CreateAccountHandler
// IdentityEnableHandler sets the operation handler for the enable operation
IdentityEnableHandler identity.EnableHandler
// IdentityLoginHandler sets the operation handler for the login operation
IdentityLoginHandler identity.LoginHandler
// TunnelTunnelHandler sets the operation handler for the tunnel operation
TunnelTunnelHandler tunnel.TunnelHandler
// TunnelUntunnelHandler sets the operation handler for the untunnel operation
@ -209,6 +214,9 @@ func (o *ZrokAPI) Validate() error {
if o.IdentityEnableHandler == nil {
unregistered = append(unregistered, "identity.EnableHandler")
}
if o.IdentityLoginHandler == nil {
unregistered = append(unregistered, "identity.LoginHandler")
}
if o.TunnelTunnelHandler == nil {
unregistered = append(unregistered, "tunnel.TunnelHandler")
}
@ -328,6 +336,10 @@ func (o *ZrokAPI) initHandlerCache() {
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login"] = identity.NewLogin(o.context, o.IdentityLoginHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/tunnel"] = tunnel.NewTunnel(o.context, o.TunnelTunnelHandler)
if o.handlers["DELETE"] == nil {
o.handlers["DELETE"] = make(map[string]http.Handler)

View File

@ -55,6 +55,23 @@ paths:
description: internal server error
schema:
$ref: "#/definitions/errorMessage"
/login:
post:
tags:
- identity
operationId: login
parameters:
- name: body
in: body
schema:
$ref: "#/definitions/loginRequest"
responses:
200:
description: login successful
schema:
$ref: "#/definitions/loginResponse"
401:
description: invalid login
/tunnel:
post:
tags:
@ -136,6 +153,15 @@ definitions:
type: string
errorMessage:
type: string
loginRequest:
type: object
properties:
email:
type: string
password:
type: string
loginResponse:
type: string
principal:
type: object
properties:

View File

@ -1,281 +0,0 @@
// Auto-generated, edits will be overwritten
import spec from './spec'
export class ServiceError extends Error {}
let options = {}
export function init(serviceOptions) {
options = serviceOptions
}
export function request(op, parameters, attempt) {
if (!attempt) attempt = 1;
return acquireRights(op, spec, options)
.then(rights => {
parameters = parameters || {}
const baseUrl = getBaseUrl(spec)
let reqInfo = { parameters, baseUrl }
if (options.processRequest) {
reqInfo = options.processRequest(op, reqInfo)
}
const req = buildRequest(op, reqInfo.baseUrl, reqInfo.parameters, rights)
return makeFetchRequest(req)
.then(res => processResponse(req, res, attempt, options), e => processError(req, e))
.then(outcome => outcome.retry ? request(op, parameters, attempt + 1) : outcome.res)
})
}
function acquireRights(op, spec, options) {
if (op.security && options.getAuthorization) {
return op.security.reduce((promise, security) => {
return promise.then(rights => {
const securityDefinition = spec.securityDefinitions[security.id]
return options.getAuthorization(security, securityDefinition, op)
.then(auth => {
rights[security.id] = auth
return rights
})
})
}, Promise.resolve({}))
}
return Promise.resolve({})
}
function makeFetchRequest(req) {
let fetchOptions = {
compress: true,
method: (req.method || 'get').toUpperCase(),
headers: req.headers,
body: req.body ? JSON.stringify(req.body) : undefined
}
if (options.fetchOptions) {
const opts = options.fetchOptions
const headers = opts.headers
? Object.assign(fetchOptions.headers, opts.headers)
: fetchOptions.headers
fetchOptions = Object.assign({}, fetchOptions, opts)
fetchOptions.headers = headers
}
let promise = fetch(req.url, fetchOptions)
return promise
}
function buildRequest(op, baseUrl, parameters, rights) {
let paramGroups = groupParams(op, parameters)
paramGroups = applyAuthorization(paramGroups, rights, spec)
const url = buildUrl(op, baseUrl, paramGroups, spec)
const headers = buildHeaders(op, paramGroups)
const body = buildBody(parameters.body)
return {
method: op.method,
url,
headers,
body
}
}
function groupParams(op, parameters) {
const groups = ['header', 'path', 'query', 'formData'].reduce((groups, name) => {
groups[name] = formatParamsGroup(groups[name])
return groups
}, parameters)
if (!groups.header) groups.header = {}
return groups
}
function formatParamsGroup(groups) {
return Object.keys(groups || {}).reduce((g, name) => {
const param = groups[name]
if (param !== undefined) {
g[name] = formatParam(param)
}
return g
}, {})
}
function formatParam(param) {
if (param === undefined || param === null) return ''
else if (param instanceof Date) return param.toJSON()
else if (Array.isArray(param)) return param
else return param.toString()
}
function buildUrl(op, baseUrl, parameters, spec) {
let url = `${baseUrl}${op.path}`
if (parameters.path) {
url = Object.keys(parameters.path)
.reduce((url, name) => url.replace(`{${name}}`, parameters.path[name]), url)
}
const query = createQueryString(parameters.query)
return url + query
}
function getBaseUrl(spec) {
return options.url || `${spec.schemes[0] || 'https'}://${spec.host}${spec.basePath}`
}
function createQueryParam(name, value) {
const v = formatParam(value)
if (v && typeof v === 'string') return `${name}=${encodeURIComponent(v)}`
return name;
}
function createQueryString(query) {
const names = Object.keys(query || {})
if (!names.length) return ''
const params = names.map(name => ({name, value: query[name]}))
.reduce((acc, value) => {
if (Array.isArray(value.value)) {
return acc.concat(value.value)
} else {
acc.push(createQueryParam(value.name, value.value))
return acc
}
}, [])
return '?' + params.sort().join('&')
}
function buildHeaders(op, parameters) {
const headers = {}
let accepts
if (op.accepts && op.accepts.length) accepts = op.accepts
else if (spec.accepts && spec.accepts.length) accepts = spec.accepts
else accepts = [ 'application/json' ]
headers.Accept = accepts.join(', ')
let contentType
if (op.contentTypes && op.contentTypes[0]) contentType = op.contentTypes[0]
else if (spec.contentTypes && spec.contentTypes[0]) contentType = spec.contentTypes[0]
if (contentType) headers['Content-Type'] = contentType
return Object.assign(headers, parameters.header)
}
function buildBody(bodyParams) {
if (bodyParams) {
if (bodyParams.body) return bodyParams.body
const key = Object.keys(bodyParams)[0]
if (key) return bodyParams[key]
}
return undefined
}
function resolveAuthHeaderName(headerName){
if (options.authorizationHeader && headerName.toLowerCase() === 'authorization') {
return options.authorizationHeader
} else {
return headerName
}
}
function applyAuthorization(req, rights, spec) {
Object.keys(rights).forEach(name => {
const rightsInfo = rights[name]
const definition = spec.securityDefinitions[name]
switch (definition.type) {
case 'basic':
const creds = `${rightsInfo.username}:${rightsInfo.password}`
const token = (typeof window !== 'undefined' && window.btoa)
? window.btoa(creds)
: new Buffer(creds).toString('base64')
req.header[resolveAuthHeaderName('Authorization')] = `Basic ${token}`
break
case 'oauth2':
req.header[resolveAuthHeaderName('Authorization')] = `Bearer ${rightsInfo.token}`
break
case 'apiKey':
if (definition.in === 'header') {
req.header[resolveAuthHeaderName(definition.name)] = rightsInfo.apiKey
} else if (definition.in === 'query') {
req.query[definition.name] = rightsInfo.apiKey
} else {
throw new Error(`Api key must be in header or query not '${definition.in}'`)
}
break
default:
throw new Error(`Security definition type '${definition.type}' not supported`)
}
})
return req
}
function processResponse(req, response, attempt, options) {
const format = response.ok ? formatResponse : formatServiceError
const contentType = response.headers.get('content-type') || ''
let parse
if (response.status === 204) {
parse = Promise.resolve()
} else if (~contentType.indexOf('json')) {
parse = response.json()
} else if (~contentType.indexOf('octet-stream')) {
parse = response.blob()
} else if (~contentType.indexOf('text')) {
parse = response.text()
} else {
parse = Promise.resolve()
}
return parse
.then(data => format(response, data, options))
.then(res => {
if (options.processResponse) return options.processResponse(req, res, attempt)
else return Promise.resolve({ res })
})
}
function formatResponse(response, data, options) {
return { raw: response, data }
}
function formatServiceError(response, data, options) {
if (options.formatServiceError) {
data = options.formatServiceError(response, data)
} else {
const serviceError = new ServiceError()
if (data) {
if (typeof data === 'string') serviceError.message = data
else {
if (data.message) serviceError.message = data.message
if (data.body) serviceError.body = data.body
else serviceError.body = data
}
if (data.code) serviceError.code = data.code
} else {
serviceError.message = response.statusText
}
serviceError.status = response.status
data = serviceError
}
return { raw: response, data, error: true }
}
function processError(req, error) {
const { processError } = options
const res = { res: { raw: {}, data: error, error: true } }
return Promise.resolve(processError ? processError(req, res) : res)
}
const COLLECTION_DELIM = { csv: ',', multi: '&', pipes: '|', ssv: ' ', tsv: '\t' }
export function formatArrayParam(array, format, name) {
if (!array) return
if (format === 'multi') return array.map(value => createQueryParam(name, value))
const delim = COLLECTION_DELIM[format]
if (!delim) throw new Error(`Invalid collection format '${format}'`)
return array.map(formatParam).join(delim)
}
export function formatDate(date, format) {
if (!date) return
const str = date.toISOString()
return (format === 'date') ? str.split('T')[0] : str
}

View File

@ -1,23 +0,0 @@
// Auto-generated, edits will be overwritten
const spec = {
'host': 'localhost',
'schemes': [
'http'
],
'basePath': '/api/v1',
'contentTypes': [
'application/zrok.v1+json'
],
'accepts': [
'application/zrok.v1+json'
],
'securityDefinitions': {
'key': {
'type': 'apiKey',
'in': 'header',
'name': 'x-token'
}
}
}
export default spec

View File

@ -1,40 +0,0 @@
/** @module identity */
// Auto-generated, edits will be overwritten
import * as gateway from './gateway'
/**
* @param {object} options Optional options
* @param {module:types.accountRequest} [options.body]
* @return {Promise<module:types.accountResponse>} account created
*/
export function createAccount(options) {
if (!options) options = {}
const parameters = {
body: {
body: options.body
}
}
return gateway.request(createAccountOperation, parameters)
}
/**
*/
export function enable() {
return gateway.request(enableOperation)
}
const createAccountOperation = {
path: '/account',
contentTypes: ['application/zrok.v1+json'],
method: 'post'
}
const enableOperation = {
path: '/enable',
method: 'post',
security: [
{
id: 'key'
}
]
}

View File

@ -1,14 +0,0 @@
/** @module metadata */
// Auto-generated, edits will be overwritten
import * as gateway from './gateway'
/**
*/
export function version() {
return gateway.request(versionOperation)
}
const versionOperation = {
path: '/version',
method: 'get'
}

View File

@ -1,55 +0,0 @@
/** @module tunnel */
// Auto-generated, edits will be overwritten
import * as gateway from './gateway'
/**
* @param {object} options Optional options
* @param {module:types.tunnelRequest} [options.body]
* @return {Promise<module:types.tunnelResponse>} tunnel created
*/
export function tunnel(options) {
if (!options) options = {}
const parameters = {
body: {
body: options.body
}
}
return gateway.request(tunnelOperation, parameters)
}
/**
* @param {object} options Optional options
* @param {module:types.untunnelRequest} [options.body]
* @return {Promise<object>} tunnel removed
*/
export function untunnel(options) {
if (!options) options = {}
const parameters = {
body: {
body: options.body
}
}
return gateway.request(untunnelOperation, parameters)
}
const tunnelOperation = {
path: '/tunnel',
contentTypes: ['application/zrok.v1+json'],
method: 'post',
security: [
{
id: 'key'
}
]
}
const untunnelOperation = {
path: '/untunnel',
contentTypes: ['application/zrok.v1+json'],
method: 'delete',
security: [
{
id: 'key'
}
]
}

View File

@ -1,56 +0,0 @@
/** @module types */
// Auto-generated, edits will be overwritten
/**
* @typedef accountRequest
* @memberof module:types
*
* @property {string} username
* @property {string} password
*/
/**
* @typedef accountResponse
* @memberof module:types
*
* @property {string} token
*/
/**
* @typedef enableResponse
* @memberof module:types
*
* @property {string} identity
* @property {string} cfg
*/
/**
* @typedef principal
* @memberof module:types
*
* @property {number} id
* @property {string} username
* @property {string} token
*/
/**
* @typedef tunnelRequest
* @memberof module:types
*
* @property {string} identity
* @property {string} endpoint
*/
/**
* @typedef tunnelResponse
* @memberof module:types
*
* @property {string} service
*/
/**
* @typedef untunnelRequest
* @memberof module:types
*
* @property {string} service
*/

View File

@ -1,4 +1,5 @@
import {useState} from "react";
import * as identity from './api/identity';
const Login = (props) => {
const [email, setEmail] = useState('');
@ -6,9 +7,21 @@ const Login = (props) => {
const handleSubmit = async e => {
e.preventDefault()
identity.login({body: {"email": email, "password": password}})
.then(resp => {
if(!resp.error) {
props.loginSuccess({
email: email,
token: resp.token
})
console.log('login succeeded', resp)
} else {
console.log('login failed')
}
})
.catch((resp) => {
console.log('login failed', resp)
});
};
return (
@ -17,7 +30,7 @@ const Login = (props) => {
<input type="text" value={email} placeholder="enter an email" onChange={({ target }) => setEmail(target.value)}/>
<div>
<label htmlFor="password">password: </label>
<input type="password" value={password} placeholder="enter a password" onChange={({ target}) => setPassword(target.value)}/>
<input type="password" value={password} placeholder="enter a password" onChange={({ target }) => setPassword(target.value)}/>
</div>
<button type="submit">Log In</button>
</form>

View File

@ -23,6 +23,21 @@ export function enable() {
return gateway.request(enableOperation)
}
/**
* @param {{password: string, email: string}} options Optional options
* @param {module:types.loginRequest} [options.body]
* @return {Promise<module:types.loginResponse>} login successful
*/
export function login(options) {
if (!options) options = {}
const parameters = {
body: {
body: options.body
}
}
return gateway.request(loginOperation, parameters)
}
const createAccountOperation = {
path: '/account',
contentTypes: ['application/zrok.v1+json'],
@ -38,3 +53,9 @@ const enableOperation = {
}
]
}
const loginOperation = {
path: '/login',
contentTypes: ['application/zrok.v1+json'],
method: 'post'
}

View File

@ -24,6 +24,14 @@
* @property {string} cfg
*/
/**
* @typedef loginRequest
* @memberof module:types
*
* @property {string} email
* @property {string} password
*/
/**
* @typedef principal
* @memberof module:types