Skip to content

Commit

Permalink
feat: only return registr token against localhost, add descriptions t…
Browse files Browse the repository at this point in the history
…o api docs

Signed-off-by: Sarah Funkhouser <[email protected]>
  • Loading branch information
golanglemonade committed Jan 26, 2025
1 parent 3b8a4a9 commit 1eabad5
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 68 deletions.
7 changes: 6 additions & 1 deletion internal/httpserve/handlers/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"context"
"net/http"
"strings"

"github.com/getkin/kin-openapi/openapi3"
"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -84,7 +85,11 @@ func (h *Handler) RegisterHandler(ctx echo.Context) error {
ID: meowuser.ID,
Email: meowuser.Email,
Message: "Welcome to Openlane!",
Token: meowtoken.Token,
}

// only return the token in development
if strings.Contains(ctx.Request().Host, "localhost") {
out.Token = meowtoken.Token
}

return h.Created(ctx, out)
Expand Down
3 changes: 1 addition & 2 deletions internal/httpserve/handlers/verifyemail.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ func (h *Handler) VerifyEmail(ctx echo.Context) error {
Reply: rout.Reply{Success: false},
ID: meowtoken.ID,
Email: user.Email,
Message: "Token expired, a new token has been issued. Please try again.",
Token: meowtoken.Token,
Message: "Token expired, a new token has been issued. Please check your email and try again.",
}

return h.Created(ctx, out)
Expand Down
20 changes: 19 additions & 1 deletion internal/httpserve/server/openapi.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package server

import (
"reflect"

"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3gen"

Expand All @@ -18,7 +20,7 @@ func NewOpenAPISpec() (*openapi3.T, error) {
securityschemes := make(openapi3.SecuritySchemes)
examples := make(openapi3.Examples)

generator := openapi3gen.NewGenerator(openapi3gen.UseAllExportedFields())
generator := openapi3gen.NewGenerator(openapi3gen.UseAllExportedFields(), customizer)
for key, val := range openAPISchemas {
ref, err := generator.NewSchemaRefForValue(val, schemas)
if err != nil {
Expand Down Expand Up @@ -160,6 +162,22 @@ func NewOpenAPISpec() (*openapi3.T, error) {
}, nil
}

var customizer = openapi3gen.SchemaCustomizer(func(name string, ft reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error {
if tag.Get("exclude") != "" && tag.Get("exclude") == "true" {
return &openapi3gen.ExcludeSchemaSentinel{}
}

if tag.Get("description") != "" {
schema.Description = tag.Get("description")
}

if tag.Get("example") != "" {
schema.Example = tag.Get("example")
}

return nil
})

// openAPISchemas is a mapping of types to auto generate schemas for - these specifically live under the OAS "schema" type so that we can simply make schemaRef's to them and not have to define them all individually in the OAS paths
var openAPISchemas = map[string]any{
"LoginRequest": &models.LoginRequest{},
Expand Down
125 changes: 61 additions & 64 deletions pkg/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import (
// =========

type AuthData struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
Session string `json:"session,omitempty"`
TokenType string `json:"token_type,omitempty"`
AccessToken string `json:"access_token" description:"The access token to be used for authentication"`
RefreshToken string `json:"refresh_token,omitempty "description:"The refresh token to be used to refresh the access token after it expires"`
Session string `json:"session,omitempty" description:"The session token to be used for authentication"`
TokenType string `json:"token_type,omitempty" description:"The type of token being returned"`
}

// =========
Expand All @@ -31,8 +31,8 @@ type AuthData struct {

// LoginRequest holds the login payload for the /login route
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username" description:"The email address associated with the account"`
Password string `json:"password" description:"The password associated with the account"`
}

// LoginReply holds the response to LoginRequest
Expand Down Expand Up @@ -82,7 +82,7 @@ var ExampleLoginSuccessResponse = LoginReply{

// RefreshRequest holds the fields that should be included on a request to the `/refresh` endpoint
type RefreshRequest struct {
RefreshToken string `json:"refresh_token"`
RefreshToken string `json:"refresh_token" description:"The refresh token to be used to refresh the access token after it expires"`
}

// RefreshReply holds the fields that are sent on a response to the `/refresh` endpoint
Expand Down Expand Up @@ -124,19 +124,19 @@ var ExampleRefreshSuccessResponse = RefreshReply{

// RegisterRequest holds the fields that should be included on a request to the `/register` endpoint
type RegisterRequest struct {
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Email string `json:"email"`
Password string `json:"password"`
FirstName string `json:"first_name,omitempty" description:"The first name of the user"`
LastName string `json:"last_name,omitempty" description:"The last name of the user"`
Email string `json:"email" description:"The email address of the user"`
Password string `json:"password" description:"The password to be used for authentication after registration"`
}

// RegisterReply holds the fields that are sent on a response to the `/register` endpoint
type RegisterReply struct {
rout.Reply
ID string `json:"user_id"`
Email string `json:"email"`
ID string `json:"user_id" description:"The ID of the user that was created"`
Email string `json:"email" description:"The email address of the user"`
Message string `json:"message"`
Token string `json:"token"`
Token string `json:"token,omitempty" exclude:"true"` // only used for requests against local development
}

// Validate ensures the required fields are set on the RegisterRequest request
Expand Down Expand Up @@ -173,7 +173,6 @@ var ExampleRegisterSuccessResponse = RegisterReply{
ID: "1234",
Email: "",
Message: "Welcome to Openlane!",
Token: "",
}

// =========
Expand All @@ -182,7 +181,7 @@ var ExampleRegisterSuccessResponse = RegisterReply{

// SwitchOrganizationRequest contains the target organization ID being switched to for the /switch endpoint
type SwitchOrganizationRequest struct {
TargetOrganizationID string `json:"target_organization_id"`
TargetOrganizationID string `json:"target_organization_id" description:"The ID of the organization to switch to"`
}

// SwitchOrganizationReply holds the new authentication and session information for the user for the new organization
Expand Down Expand Up @@ -224,15 +223,14 @@ var ExampleSwitchSuccessReply = SwitchOrganizationReply{

// VerifyRequest holds the fields that should be included on a request to the `/verify` endpoint
type VerifyRequest struct {
Token string `query:"token"`
Token string `query:"token" description:"The token to be used to verify the email address, token is sent in the email"`
}

// VerifyReply holds the fields that are sent on a response to the `/verify` endpoint
type VerifyReply struct {
rout.Reply
ID string `json:"user_id"`
Email string `json:"email"`
Token string `json:"token"`
ID string `json:"user_id" description:"The ID of the user that was created"`
Email string `json:"email" description:"The email address of the user"`
Message string `json:"message,omitempty"`
AuthData
}
Expand All @@ -258,7 +256,6 @@ var ExampleVerifySuccessResponse = VerifyReply{
},
ID: ulids.New().String(),
Email: "[email protected]",
Token: "token",
Message: "Email has been verified",
AuthData: AuthData{
AccessToken: "access_token",
Expand All @@ -274,7 +271,7 @@ var ExampleVerifySuccessResponse = VerifyReply{

// ResendRequest contains fields for a resend email verification request to the `/resend` endpoint
type ResendRequest struct {
Email string `json:"email"`
Email string `json:"email" description:"The email address to resend the verification email to, must match the email address on the account"`
}

// ResendReply holds the fields that are sent on a response to the `/resend` endpoint
Expand Down Expand Up @@ -311,7 +308,7 @@ var ExampleResendEmailSuccessResponse = ResendReply{

// ForgotPasswordRequest contains fields for a forgot password request
type ForgotPasswordRequest struct {
Email string `json:"email"`
Email string `json:"email" description:"The email address associated with the account to send the password reset email to"`
}

// ForgotPasswordReply contains fields for a forgot password response
Expand Down Expand Up @@ -348,8 +345,8 @@ var ExampleForgotPasswordSuccessResponse = ForgotPasswordReply{

// ResetPasswordRequest contains user input required to reset a user's password using /password-reset endpoint
type ResetPasswordRequest struct {
Password string `json:"password"`
Token string `json:"token"`
Password string `json:"password" description:"The new password to be used for authentication"`
Token string `json:"token" description:"The token to be used to reset the password, token is sent in the email"`
}

// ResetPasswordReply is the response returned from a non-successful password reset request
Expand Down Expand Up @@ -395,8 +392,8 @@ var ExampleResetPasswordSuccessResponse = ResetPasswordReply{

// WebauthnRegistrationRequest is the request to begin a webauthn login
type WebauthnRegistrationRequest struct {
Email string `json:"email"`
Name string `json:"name"`
Email string `json:"email" description:"The email address associated with the account"`
Name string `json:"name" description:"The name of the user"`
}

func (r *WebauthnRegistrationRequest) Validate() error {
Expand Down Expand Up @@ -464,7 +461,7 @@ type WebauthnLoginResponse struct {

// VerifySubscribeRequest holds the fields that should be included on a request to the `/subscribe/verify` endpoint
type VerifySubscribeRequest struct {
Token string `query:"token"`
Token string `query:"token" description:"The token to be used to verify the subscription, token is sent in the email"`
}

// VerifySubscribeReply holds the fields that are sent on a response to the `/subscribe/verify` endpoint
Expand Down Expand Up @@ -499,17 +496,17 @@ var ExampleVerifySubscriptionResponse = VerifySubscribeReply{

// InviteRequest holds the fields that should be included on a request to the `/invite` endpoint
type InviteRequest struct {
Token string `query:"token"`
Token string `query:"token" description:"The token to be used to accept the invitation"`
}

// InviteReply holds the fields that are sent on a response to an accepted invitation
type InviteReply struct {
rout.Reply
ID string `json:"user_id"`
Email string `json:"email"`
ID string `json:"user_id" description:"The ID of the user that was created"`
Email string `json:"email" description:"The email address of the user"`
Message string `json:"message"`
JoinedOrgID string `json:"joined_org_id"`
Role string `json:"role"`
JoinedOrgID string `json:"joined_org_id" description:"The ID of the organization the user joined"`
Role string `json:"role" description:"The role the user has in the organization"`
AuthData
}

Expand Down Expand Up @@ -549,13 +546,13 @@ var ExampleInviteResponse = InviteReply{

// OauthTokenRequest to authenticate an oauth user with the Server
type OauthTokenRequest struct {
Name string `json:"name"`
Email string `json:"email"`
AuthProvider string `json:"authProvider"`
ExternalUserID string `json:"externalUserId"`
ExternalUserName string `json:"externalUserName"`
ClientToken string `json:"clientToken"`
Image string `json:"image,omitempty"`
Name string `json:"name" description:"The name of the user"`
Email string `json:"email" description:"The email address of the user"`
AuthProvider string `json:"authProvider" description:"The provider used to authenticate the user, e.g. google, github, etc."`
ExternalUserID string `json:"externalUserId" description:"The ID of the user from the external provider"`
ExternalUserName string `json:"externalUserName" description:"The username of the user from the external provider"`
ClientToken string `json:"clientToken" description:"The token provided by the external provider"`
Image string `json:"image,omitempty" description:"The image URL of the user from the external provider"`
}

// =========
Expand All @@ -564,10 +561,10 @@ type OauthTokenRequest struct {

// AccountAccessRequest holds the fields that should be included on a request to the `/account/access` endpoint
type AccountAccessRequest struct {
ObjectID string `json:"objectId"`
ObjectType string `json:"objectType"`
Relation string `json:"relation"`
SubjectType string `json:"subjectType,omitempty"`
ObjectID string `json:"objectId" description:"The ID of the object to check access for"`
ObjectType string `json:"objectType" description:"The type of object to check access for, e.g. organization, program, procedure, etc"`
Relation string `json:"relation" description:"The relation to check access for, e.g. can_view, can_edit"`
SubjectType string `json:"subjectType,omitempty" description:"The type of subject to check access for, e.g. service, user"`
}

// AccountAccessReply holds the fields that are sent on a response to the `/account/access` endpoint
Expand Down Expand Up @@ -617,10 +614,10 @@ var ExampleAccountAccessReply = AccountAccessReply{

// AccountRolesRequest holds the fields that should be included on a request to the `/account/roles` endpoint
type AccountRolesRequest struct {
ObjectID string `json:"objectId"`
ObjectType string `json:"objectType"`
SubjectType string `json:"subjectType,omitempty"`
Relations []string `json:"relations,omitempty"`
ObjectID string `json:"objectId" description:"The ID of the object to check roles for"`
ObjectType string `json:"objectType" description:"The type of object to check roles for, e.g. organization, program, procedure, etc"`
SubjectType string `json:"subjectType,omitempty" description:"The type of subject to check roles for, e.g. service, user"`
Relations []string `json:"relations,omitempty" description:"The relations to check roles for, e.g. can_view, can_edit"`
}

// AccountRolesReply holds the fields that are sent on a response to the `/account/roles` endpoint
Expand Down Expand Up @@ -665,13 +662,13 @@ var ExampleAccountRolesReply = AccountRolesReply{

// AccountRolesOrganizationRequest holds the fields that should be included on a request to the `/account/roles/organization` endpoint
type AccountRolesOrganizationRequest struct {
ID string `param:"id"`
ID string `param:"id" description:"The ID of the organization to check roles for"`
}

// AccountRolesOrganizationReply holds the fields that are sent on a response to the `/account/roles/organization` endpoint
type AccountRolesOrganizationReply struct {
rout.Reply
Roles []string `json:"roles"`
Roles []string `json:"roles" description:"The roles the user has in the organization, e.g. can_view, can_edit"`
OrganizationID string `json:"organization_id"`
}

Expand Down Expand Up @@ -702,28 +699,28 @@ var ExampleAccountRolesOrganizationReply = AccountRolesOrganizationReply{

// UploadFilesRequest holds the fields that should be included on a request to the `/upload` endpoint
type UploadFilesRequest struct {
UploadFile multipart.FileHeader `form:"uploadFile"`
UploadFile multipart.FileHeader `form:"uploadFile" description:"The file to be uploaded"`
}

// UploadFilesReply holds the fields that are sent on a response to the `/upload` endpoint
type UploadFilesReply struct {
rout.Reply
Message string `json:"message,omitempty"`
FileCount int64 `json:"file_count,omitempty"`
Files []File `json:"files,omitempty"`
FileCount int64 `json:"file_count,omitempty" description:"The number of files uploaded"`
Files []File `json:"files,omitempty" description:"The files that were uploaded"`
}

// File holds the fields that are sent on a response to the `/upload` endpoint
type File struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Size int64 `json:"size,omitempty"`
MimeType string `json:"mime_type,omitempty"`
ContentType string `json:"content_type,omitempty"`
PresignedURL string `json:"presigned_url,omitempty"`
MD5 []byte `json:"md5,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
ID string `json:"id,omitempty" description:"The ID of the uploaded file"`
Name string `json:"name,omitempty" description:"The name of the uploaded file"`
Size int64 `json:"size,omitempty" description:"The size of the uploaded file in bytes"`
MimeType string `json:"mime_type,omitempty" description:"The mime type of the uploaded file"`
ContentType string `json:"content_type,omitempty "description:"The content type of the uploaded file"`
PresignedURL string `json:"presigned_url,omitempty" description:"The presigned URL to download the file"`
MD5 []byte `json:"md5,omitempty" description:"The MD5 hash of the uploaded file"`
CreatedAt time.Time `json:"created_at,omitempty" description:"The time the file was uploaded"`
UpdatedAt time.Time `json:"updated_at,omitempty" description:"The time the file was last updated"`
}

// ExampleUploadFileRequest is an example of a successful upload request for OpenAPI documentation
Expand Down Expand Up @@ -765,8 +762,8 @@ var ExampleUploadFilesSuccessResponse = UploadFilesReply{

// TFARequest holds the payload for verifying the 2fa code (/2fa/validate)
type TFARequest struct {
TOTPCode string `json:"totp_code,omitempty"`
RecoveryCode string `json:"recovery_code,omitempty"`
TOTPCode string `json:"totp_code,omitempty" description:"The TOTP code to validate, always takes precedence over recovery code"`
RecoveryCode string `json:"recovery_code,omitempty" description:"The recovery code to validate, only used if TOTP code is not provided"`
}

// TFAReply holds the response to TFARequest
Expand Down

0 comments on commit 1eabad5

Please sign in to comment.