Skip to content

Dev #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 95 commits into from
Jan 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
4c9f4c8
Added revere proxy for local development testing
daystram Dec 27, 2020
c904787
Added url param encoder deps
daystram Dec 27, 2020
c4d0e9d
Updated authorization endpoint to return composed redirect url
daystram Dec 27, 2020
3dd9dd6
Fixed incorrect application query by clientID
daystram Dec 27, 2020
8759801
Standardized token endpoint to accept x-www-form-urlencoded
daystram Dec 27, 2020
1b2371c
Updated swagger host to suit reverse proxy
daystram Dec 27, 2020
02ae8ab
Added api requesters
daystram Dec 27, 2020
7d81458
Added vuelidate plugin
daystram Dec 27, 2020
3987eb1
Added status enum mixins
daystram Dec 27, 2020
5116f8b
Implemented Authorize login page
daystram Dec 27, 2020
7ab582f
Added query by email, fixed subject field query name
daystram Dec 29, 2020
49b2807
Added user query by username and email handlers
daystram Dec 29, 2020
bd4f76a
Added endpoint to check form unique fields
daystram Dec 29, 2020
b8c688e
Allowed no refresh_token generation for PKCE flow, subject stored tog…
daystram Dec 29, 2020
9cf79ce
Subject stored together with associated authorization_code
daystram Dec 29, 2020
0a980bd
Added token info introspection handler
daystram Dec 29, 2020
115799b
Updated handler interface
daystram Dec 29, 2020
f583144
Added token introspection endpoint
daystram Dec 29, 2020
e130209
Updated user authentication handler to return user model
daystram Dec 29, 2020
88d8f86
Refactored api response package name
daystram Dec 29, 2020
4158dbc
Updated authorization flows to include subject for token handlers, re…
daystram Dec 29, 2020
3e1f19f
Updated user response package field names
daystram Dec 29, 2020
abd0c5f
Replaced auth middleware to use self-issued OAuth tokens
daystram Dec 29, 2020
4031c91
Added deps, disabled host check for tunnelled tests
daystram Dec 29, 2020
dd15e37
Added global styles
daystram Dec 29, 2020
6f1df7d
Added token request oauth api for PKCE flow
daystram Dec 29, 2020
3c807a0
Created AuthManager library to interface with OAuth flows
daystram Dec 29, 2020
05fec7a
Added auth helper functions
daystram Dec 29, 2020
f9288a0
Added theme configuration
daystram Dec 29, 2020
b63a147
Added mock Manage, Profile and User pages
daystram Dec 29, 2020
f838edb
Cleaned up data definitions, added mock timeouts during form submission
daystram Dec 29, 2020
2ef0a7c
Refactored form styles, added success alert, added redirect counter n…
daystram Dec 29, 2020
a60f133
Bundled view exports
daystram Dec 29, 2020
a7d9d88
Added application description, added sample contextual authed content
daystram Dec 29, 2020
3efcb3e
Updated router, added route restriction
daystram Dec 29, 2020
cf7b763
Added Signup page, added user registration api
daystram Dec 29, 2020
162011e
Moved user signup endpoint to user's group
daystram Dec 29, 2020
379b39d
Added post submission error feedback
daystram Dec 29, 2020
9e37d4e
Added unique field pre-checks, added post-submission error codes
daystram Dec 29, 2020
636758b
Added error codes for authorize endpoint
daystram Dec 30, 2020
1889fb2
Improved styling and transitions
daystram Dec 30, 2020
585cac1
Added post-submission error handling
daystram Dec 30, 2020
8044b17
Implemented base Manage page
daystram Dec 30, 2020
39c0973
Fixed data referencing error
daystram Dec 30, 2020
4d48aba
Added scopes field for authorization flow, added user info parsing fr…
daystram Dec 30, 2020
f8be545
User menu in manage retrieves user info from token
daystram Dec 30, 2020
31e7bec
Added OID claims struct, added scope field to authRequest
daystram Dec 30, 2020
4d207c4
Generalized jwt generator util
daystram Dec 30, 2020
0aabbb0
Implemented id_token generator handler
daystram Dec 30, 2020
a263901
Added missing fields for user registration
daystram Dec 30, 2020
91a1599
Refactored token storage structure to use hashsets
daystram Dec 30, 2020
603d16f
Added id_token generation in token requests
daystram Dec 30, 2020
a2fe295
Added application list api and user detail update api
daystram Dec 31, 2020
63b2b6d
Added User object interface, moved authenticated flag to library
daystram Dec 31, 2020
bb04cb3
Added login transition, cleaned up styling
daystram Dec 31, 2020
3a30bdc
Updated mock Dashboard page, added placeholder page
daystram Dec 31, 2020
7d77dae
Implemented Profile page
daystram Jan 1, 2021
5d871d7
Fixed preloading spinner, added superuser context on sidebar nav
daystram Jan 1, 2021
e4e6c5e
Implemented Application page
daystram Jan 1, 2021
b4d11b6
Added scopes field, fixed username key name
daystram Jan 1, 2021
f2ef807
Added all applications query
daystram Jan 1, 2021
8022b4b
Fixed user update selection pipeline
daystram Jan 1, 2021
2d48904
Added all application retrieval handler
daystram Jan 1, 2021
a48fadb
Added missing user update fields
daystram Jan 1, 2021
123ee31
Fixed token introspection redis value parsing
daystram Jan 1, 2021
b1f8084
Fixed user update email checking
daystram Jan 1, 2021
6420bf4
Updated application query to return all applications
daystram Jan 1, 2021
050c130
Added Application page preloading spinner
daystram Jan 1, 2021
d9556da
Cleaned up styling
daystram Jan 1, 2021
930f647
Implemented new application creation
daystram Jan 1, 2021
cf14578
Updated application db char length
daystram Jan 1, 2021
1ce26c3
Added application detail and update apis
daystram Jan 2, 2021
974cf5a
Restructured views hierarchy
daystram Jan 2, 2021
46fc269
Implemented ApplicationDetail page
daystram Jan 2, 2021
e0cd824
Clean up views index
daystram Jan 2, 2021
d14a93c
Fixed inconsistent auth middleware unauthorized response
daystram Jan 2, 2021
a934c58
Added application locked flag, removed client secret response
daystram Jan 2, 2021
b6b8b29
Allowed all superuser to edit application
daystram Jan 2, 2021
8923aa6
Added application delete endpoint
daystram Jan 2, 2021
350f091
Added application delete api
daystram Jan 2, 2021
6e73b04
Cleaned up styling
daystram Jan 2, 2021
dd7584b
Added application delete prompt
daystram Jan 2, 2021
8aa3da4
Added client secret confirmation step after application registration
daystram Jan 2, 2021
50a4047
Return client secret on application registration
daystram Jan 2, 2021
6e7df9e
Updated retrieve user handler
daystram Jan 2, 2021
eb69479
Added application client_secret revoke handler
daystram Jan 2, 2021
48c7e92
Added application client_secret revoke endpoint
daystram Jan 2, 2021
6f620b1
Added client_secret revoke api
daystram Jan 2, 2021
26cef60
Fixed transition jitter
daystram Jan 2, 2021
398197a
Implemented client_secret renewal flow
daystram Jan 2, 2021
d378892
Encrypted client_secret
daystram Jan 2, 2021
a490729
Restricted locked application update and delete
daystram Jan 2, 2021
9609696
Enforced application locked flag
daystram Jan 2, 2021
84b37a2
Fixed authorization token injection
daystram Jan 2, 2021
c597744
Fixed missing data field
daystram Jan 2, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ services:
volumes:
- /opt/redis:/data
restart: always
proxy:
image: nginx:1.18-alpine
ports:
- "80:80"
extra_hosts:
- "host:${LOCAL_IP}"
volumes:
- /opt/nginx:/etc/nginx
restart: always

28 changes: 28 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
events {}
http {
server {
listen 80;
server_name localhost;

location / {
proxy_pass http://host:8080;
proxy_set_header Host $host;
}

location /oauth {
proxy_pass http://host:9000;
proxy_set_header Host $host;
}

location /api {
proxy_pass http://host:9000;
proxy_set_header Host $host;
}

location /docs {
proxy_pass http://host:9000;
proxy_set_header Host $host;
}
}
}

35 changes: 15 additions & 20 deletions ratify-be/controllers/middleware/auth.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,37 @@
package middleware

import (
"errors"
"fmt"
"net/http"
"strings"

"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"

"github.com/daystram/ratify/ratify-be/config"
"github.com/daystram/ratify/ratify-be/constants"
"github.com/daystram/ratify/ratify-be/datatransfers"
"github.com/daystram/ratify/ratify-be/handlers"
"github.com/daystram/ratify/ratify-be/models"
)

func AuthMiddleware(c *gin.Context) {
token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
if token == "" {
accessToken := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
if accessToken == "" {
c.Set(constants.IsAuthenticatedKey, false)
c.Next()
return
}
claims, err := parseToken(token, config.AppConfig.JWTSecret)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, datatransfers.Response{Error: err.Error()})
var err error
var tokenInfo datatransfers.TokenIntrospection
if tokenInfo, err = handlers.Handler.IntrospectAccessToken(accessToken); err != nil || !tokenInfo.Active {
c.AbortWithStatusJSON(http.StatusUnauthorized, datatransfers.APIResponse{Code: "invalid_token", Error: "invalid access_token"})
return
}
var user models.User
if user, err = handlers.Handler.RetrieveUserBySubject(tokenInfo.Subject); err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, datatransfers.APIResponse{Code: "invalid_token", Error: err.Error()})
return
}
c.Set(constants.IsAuthenticatedKey, true)
c.Set(constants.UserSubjectKey, claims.Subject)
c.Set(constants.IsSuperuserKey, claims.IsSuperuser)
c.Set(constants.UserSubjectKey, user.Subject)
c.Set(constants.IsSuperuserKey, user.Superuser)
c.Next()
}

func parseToken(tokenString, secret string) (claims datatransfers.JWTClaims, err error) {
if token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
}); err != nil || !token.Valid {
return datatransfers.JWTClaims{}, errors.New(fmt.Sprintf("invalid token. %s", err))
}
return
}
128 changes: 92 additions & 36 deletions ratify-be/controllers/v1/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,45 @@ import (
// @Router /api/v1/application/{client_id} [GET]
func GETOneApplicationDetail(c *gin.Context) {
var err error
// fetch clientID
clientID := strings.TrimPrefix(c.Param("client_id"), "/") // trim due to router catch-all
// get application
// fetch application info
var application models.Application
if application, err = handlers.Handler.RetrieveApplication(clientID); err != nil {
c.JSON(http.StatusNotFound, datatransfers.Response{Error: "application not found"})
application.ClientID = strings.TrimPrefix(c.Param("client_id"), "/") // trim due to router catch-all
if application, err = handlers.Handler.RetrieveApplication(application.ClientID); err != nil {
c.JSON(http.StatusNotFound, datatransfers.APIResponse{Error: "application not found"})
return
}
// check ownership
if application.Owner.Subject != c.GetString(constants.UserSubjectKey) {
c.JSON(http.StatusOK, datatransfers.Response{Data: datatransfers.ApplicationInfo{
// check superuser
if !c.GetBool(constants.IsSuperuserKey) {
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: datatransfers.ApplicationInfo{
Name: application.Name,
}})
return
}
c.JSON(http.StatusOK, datatransfers.Response{Data: datatransfers.ApplicationInfo{
ClientID: application.ClientID,
ClientSecret: application.ClientSecret,
Name: application.Name,
Description: application.Description,
LoginURL: application.LoginURL,
CallbackURL: application.CallbackURL,
LogoutURL: application.LogoutURL,
Metadata: application.Metadata,
CreatedAt: application.CreatedAt,
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: datatransfers.ApplicationInfo{
ClientID: application.ClientID,
Name: application.Name,
Description: application.Description,
LoginURL: application.LoginURL,
CallbackURL: application.CallbackURL,
LogoutURL: application.LogoutURL,
Metadata: application.Metadata,
Locked: application.Locked,
CreatedAt: application.CreatedAt,
}})
return
}

// @Summary Get owned applications
// @Summary Get all applications
// @Tags application
// @Security BearerAuth
// @Success 200 "OK"
// @Router /api/v1/application [GET]
func GETOwnedApplications(c *gin.Context) {
func GETApplicationList(c *gin.Context) {
var err error
// get all owned applications
var applications []models.Application
if applications, err = handlers.Handler.RetrieveOwnedApplications(c.GetString(constants.UserSubjectKey)); err != nil {
c.JSON(http.StatusNotFound, datatransfers.Response{Error: "cannot retrieve applications"})
if applications, err = handlers.Handler.RetrieveAllApplications(); err != nil {
c.JSON(http.StatusNotFound, datatransfers.APIResponse{Error: "cannot retrieve applications"})
return
}
var applicationsResponse []datatransfers.ApplicationInfo
Expand All @@ -72,7 +71,7 @@ func GETOwnedApplications(c *gin.Context) {
CreatedAt: application.CreatedAt,
})
}
c.JSON(http.StatusOK, datatransfers.Response{Data: applicationsResponse})
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: applicationsResponse})
return
}

Expand All @@ -87,15 +86,16 @@ func POSTApplication(c *gin.Context) {
// fetch application info
var applicationInfo datatransfers.ApplicationInfo
if err = c.ShouldBindJSON(&applicationInfo); err != nil {
c.JSON(http.StatusBadRequest, datatransfers.Response{Error: err.Error()})
c.JSON(http.StatusBadRequest, datatransfers.APIResponse{Error: err.Error()})
return
}
// register application
if applicationInfo.ClientID, err = handlers.Handler.RegisterApplication(applicationInfo, c.GetString(constants.UserSubjectKey)); err != nil {
c.JSON(http.StatusInternalServerError, datatransfers.Response{Error: "failed updating application"})
var clientSecret string
if applicationInfo.ClientID, clientSecret, err = handlers.Handler.RegisterApplication(applicationInfo, c.GetString(constants.UserSubjectKey)); err != nil {
c.JSON(http.StatusInternalServerError, datatransfers.APIResponse{Error: "failed updating application"})
return
}
c.JSON(http.StatusOK, datatransfers.Response{Data: gin.H{"client_id": applicationInfo.ClientID}})
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: gin.H{"client_id": applicationInfo.ClientID, "client_secret": clientSecret}})
return
}

Expand All @@ -109,27 +109,83 @@ func POSTApplication(c *gin.Context) {
func PUTApplication(c *gin.Context) {
var err error
// fetch application info
clientID := c.Param("client_id")
var applicationInfo datatransfers.ApplicationInfo
if err = c.ShouldBindJSON(&applicationInfo); err != nil {
c.JSON(http.StatusBadRequest, datatransfers.Response{Error: err.Error()})
c.JSON(http.StatusBadRequest, datatransfers.APIResponse{Error: err.Error()})
return
}
// check ownership
var application models.Application
if application, err = handlers.Handler.RetrieveApplication(clientID); err != nil {
c.JSON(http.StatusNotFound, datatransfers.Response{Error: "application not found"})
application.ClientID = strings.TrimPrefix(c.Param("client_id"), "/")
if application, err = handlers.Handler.RetrieveApplication(application.ClientID); err != nil {
c.JSON(http.StatusNotFound, datatransfers.APIResponse{Error: "application not found"})
return
}
if application.Owner.Subject != c.GetString(constants.UserSubjectKey) {
c.JSON(http.StatusUnauthorized, datatransfers.Response{Error: "access to resource unauthorized"})
// checked locked flag
if application.Locked &&
(applicationInfo.LoginURL != application.LoginURL ||
applicationInfo.CallbackURL != application.CallbackURL ||
applicationInfo.LogoutURL != application.LogoutURL) {
c.JSON(http.StatusBadRequest, datatransfers.APIResponse{Error: "application is locked"})
return
}
// update application
if err = handlers.Handler.UpdateApplication(applicationInfo); err != nil {
c.JSON(http.StatusInternalServerError, datatransfers.Response{Error: "failed updating application"})
c.JSON(http.StatusInternalServerError, datatransfers.APIResponse{Error: "failed updating application"})
return
}
c.JSON(http.StatusOK, datatransfers.APIResponse{})
return
}

// @Summary Delete application
// @Tags application
// @Security BearerAuth
// @Param client_id path string true "Client ID"
// @Success 200 "OK"
// @Router /api/v1/application/{client_id} [DELETE]
func DELETEApplication(c *gin.Context) {
var err error
// fetch application info
var application models.Application
application.ClientID = strings.TrimPrefix(c.Param("client_id"), "/")
if application, err = handlers.Handler.RetrieveApplication(application.ClientID); err != nil {
c.JSON(http.StatusNotFound, datatransfers.APIResponse{Error: "application not found"})
return
}
// checked locked flag
if application.Locked {
c.JSON(http.StatusBadRequest, datatransfers.APIResponse{Error: "application is locked"})
return
}
// delete application
if err = handlers.Handler.DeleteApplication(application.ClientID); err != nil {
c.JSON(http.StatusInternalServerError, datatransfers.APIResponse{Error: "failed deleting application"})
return
}
c.JSON(http.StatusOK, datatransfers.APIResponse{})
return
}

// @Summary Revoke application secret
// @Tags application
// @Security BearerAuth
// @Param client_id path string true "Client ID"
// @Success 200 "OK"
// @Router /api/v1/application/{client_id}/revoke [PUT]
func PUTApplicationRevokeSecret(c *gin.Context) {
var err error
// fetch application info
clientID := strings.TrimPrefix(c.Param("client_id"), "/")
if _, err = handlers.Handler.RetrieveApplication(clientID); err != nil {
c.JSON(http.StatusNotFound, datatransfers.APIResponse{Error: "application not found"})
return
}
// renew application client_secret
var clientSecret string
if clientSecret, err = handlers.Handler.RenewApplicationClientSecret(clientID); err != nil {
c.JSON(http.StatusInternalServerError, datatransfers.APIResponse{Error: "failed renewing application client_secret"})
return
}
c.JSON(http.StatusOK, datatransfers.Response{})
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: gin.H{"client_secret": clientSecret}})
return
}
52 changes: 0 additions & 52 deletions ratify-be/controllers/v1/auth.go

This file was deleted.

39 changes: 39 additions & 0 deletions ratify-be/controllers/v1/form.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package v1

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"

"github.com/daystram/ratify/ratify-be/datatransfers"
"github.com/daystram/ratify/ratify-be/handlers"
)

// @Summary Check unique form field
// @Tags form
// @Param uniqueRequest body datatransfers.UniqueCheckRequest true "Unique query"
// @Success 200 "OK"
// @Router /api/v1/form/unique [POST]
func POSTUniqueCheck(c *gin.Context) {
var err error
var uniqueRequest datatransfers.UniqueCheckRequest
if err = c.ShouldBindJSON(&uniqueRequest); err != nil {
c.JSON(http.StatusBadRequest, datatransfers.APIResponse{Error: err.Error()})
return
}
unique := false
switch uniqueRequest.Field {
case "user:username":
_, err = handlers.Handler.RetrieveUserByUsername(uniqueRequest.Value)
unique = err != nil
case "user:email":
_, err = handlers.Handler.RetrieveUserByEmail(uniqueRequest.Value)
unique = err != nil
default:
c.JSON(http.StatusBadRequest, datatransfers.APIResponse{Error: fmt.Sprintf("unsupported field %s", uniqueRequest.Field)})
return
}
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: unique})
return
}
Loading