Skip to content

Commit

Permalink
feat(core): add demo mode (#38)
Browse files Browse the repository at this point in the history
* feat: add demo mode

* ci: enable demo mode for demo website
  • Loading branch information
ayuhito authored Jun 23, 2024
1 parent 6ea3bec commit e2595f0
Show file tree
Hide file tree
Showing 17 changed files with 182 additions and 17 deletions.
56 changes: 52 additions & 4 deletions core/api/oas_response_encoders_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion core/api/oas_schemas_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions core/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type ServerConfig struct {

// Misc settings.
UseEnvironment bool
DemoMode bool `env:"DEMO_MODE"`
}

type AppDBConfig struct {
Expand Down Expand Up @@ -55,6 +56,9 @@ const (
// Logging constants.
DefaultLogger = "json"
DefaultLoggerLevel = "info"

// Misc constants.
DefaultDemoMode = false
)

// NewServerConfig creates a new server config.
Expand All @@ -68,6 +72,7 @@ func NewServerConfig(useEnv bool) (*ServerConfig, error) {
TimeoutWrite: DefaultTimeoutWrite,
TimeoutIdle: DefaultTimeoutIdle,
UseEnvironment: useEnv,
DemoMode: DefaultDemoMode,
}

// Load config from environment variables.
Expand Down
3 changes: 2 additions & 1 deletion core/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func (s *StartCommand) ParseFlags(args []string) error {

// Misc settings.
fs.BoolVar(&s.Server.UseEnvironment, "env", false, "Opt-in to allow environment variables to be used for configuration. Flags will still override environment variables.")
fs.BoolVar(&s.Server.DemoMode, "demo", s.Server.DemoMode, "Enable demo mode restricting all POST/PATCH/DELETE actions (except login).")

// Handle array type flags.
corsAllowedOrigins := fs.String("corsorigins", strings.Join(s.Server.CORSAllowedOrigins, ","), "Comma separated list of allowed CORS origins on API routes. Useful for external dashboards that may host the frontend on a different domain.")
Expand Down Expand Up @@ -121,7 +122,7 @@ func (s *StartCommand) Run(ctx context.Context) error {
}

// Setup auth service
auth, err := util.NewAuthService(ctx)
auth, err := util.NewAuthService(ctx, s.Server.DemoMode)
if err != nil {
return errors.Wrap(err, "failed to create auth service")
}
Expand Down
2 changes: 1 addition & 1 deletion core/migrations/0001_sqlite_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func Up0001(c *sqlite.Client) error {
id := typeid.String()

// Hash default password
auth, err := util.NewAuthService(context.Background())
auth, err := util.NewAuthService(context.Background(), false)
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions core/model/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var (
ErrInternalServerError = errors.New("internal server error")

// Authentication
// ErrDemoMode is returned when a user tries to perform an action in demo mode.
ErrDemoMode = errors.New("user in demo mode")
// ErrInvalidSession is returned when a session is invalid.
ErrInvalidSession = errors.New("invalid session")
// ErrSessionNotFound is returned when a session is not found.
Expand Down
12 changes: 10 additions & 2 deletions core/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ paths:
$ref: "#/components/responses/BadRequestError"
"401":
$ref: "#/components/responses/UnauthorisedError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
Expand All @@ -248,10 +250,10 @@ paths:
$ref: "#/components/responses/BadRequestError"
"401":
$ref: "#/components/responses/UnauthorisedError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"500":
$ref: "#/components/responses/InternalServerError"
servers:
Expand Down Expand Up @@ -327,6 +329,8 @@ paths:
$ref: "#/components/responses/BadRequestError"
"401":
$ref: "#/components/responses/UnauthorisedError"
"403":
$ref: "#/components/responses/ForbiddenError"
"409":
$ref: "#/components/responses/ConflictError"
"500":
Expand Down Expand Up @@ -393,6 +397,8 @@ paths:
$ref: "#/components/responses/BadRequestError"
"401":
$ref: "#/components/responses/UnauthorisedError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"500":
Expand All @@ -418,6 +424,8 @@ paths:
$ref: "#/components/responses/BadRequestError"
"401":
$ref: "#/components/responses/UnauthorisedError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"500":
Expand Down
10 changes: 10 additions & 0 deletions core/services/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ func (h *Handler) GetUser(ctx context.Context, params api.GetUserParams) (api.Ge

func (h *Handler) PatchUser(ctx context.Context, req *api.UserPatch, params api.PatchUserParams) (api.PatchUserRes, error) {
log := logger.Get()
if h.auth.IsDemoMode {
log.Debug().Msg("patch user rejected in demo mode")
return ErrForbidden(model.ErrDemoMode), nil
}

// Get user id from request context and check if user exists
userId, ok := ctx.Value(model.ContextKeyUserID).(string)
if !ok {
Expand Down Expand Up @@ -108,6 +113,11 @@ func (h *Handler) PatchUser(ctx context.Context, req *api.UserPatch, params api.

func (h *Handler) DeleteUser(ctx context.Context, params api.DeleteUserParams) (api.DeleteUserRes, error) {
log := logger.Get()
if h.auth.IsDemoMode {
log.Debug().Msg("delete user rejected in demo mode")
return ErrForbidden(model.ErrDemoMode), nil
}

// Get user id from request context and check if user exists
userId, ok := ctx.Value(model.ContextKeyUserID).(string)
if !ok {
Expand Down
19 changes: 19 additions & 0 deletions core/services/websites.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ import (
"github.com/go-faster/errors"
"github.com/medama-io/medama/api"
"github.com/medama-io/medama/model"
"github.com/medama-io/medama/util/logger"
)

func (h *Handler) DeleteWebsitesID(ctx context.Context, params api.DeleteWebsitesIDParams) (api.DeleteWebsitesIDRes, error) {
log := logger.Get()
if h.auth.IsDemoMode {
log.Debug().Msg("delete website rejected in demo mode")
return ErrForbidden(model.ErrDemoMode), nil
}

// Check if user owns website
userId, ok := ctx.Value(model.ContextKeyUserID).(string)
if !ok {
Expand Down Expand Up @@ -138,6 +145,12 @@ func (h *Handler) GetWebsitesID(ctx context.Context, params api.GetWebsitesIDPar
}

func (h *Handler) PatchWebsitesID(ctx context.Context, req *api.WebsitePatch, params api.PatchWebsitesIDParams) (api.PatchWebsitesIDRes, error) {
log := logger.Get()
if h.auth.IsDemoMode {
log.Debug().Msg("patch website rejected in demo mode")
return ErrForbidden(model.ErrDemoMode), nil
}

// Get user ID from context
userId, ok := ctx.Value(model.ContextKeyUserID).(string)
if !ok {
Expand Down Expand Up @@ -187,6 +200,12 @@ func (h *Handler) PatchWebsitesID(ctx context.Context, req *api.WebsitePatch, pa
}

func (h *Handler) PostWebsites(ctx context.Context, req *api.WebsiteCreate) (api.PostWebsitesRes, error) {
log := logger.Get()
if h.auth.IsDemoMode {
log.Debug().Msg("post website rejected in demo mode")
return ErrForbidden(model.ErrDemoMode), nil
}

// Get user ID from context
userId, ok := ctx.Value(model.ContextKeyUserID).(string)
if !ok {
Expand Down
9 changes: 6 additions & 3 deletions core/util/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ type AuthService struct {
Cache *Cache
// Key used to encrypt session tokens.
aes32Key []byte
// Demo mode flag.
IsDemoMode bool
}

// NewAuthService returns a new instance of AuthService.
func NewAuthService(ctx context.Context) (*AuthService, error) {
func NewAuthService(ctx context.Context, isDemoMode bool) (*AuthService, error) {
// Generate a new random key for encrypting session tokens.
// Since we store sessions in an in-memory cache, it doesn't
// matter if the key doesn't persist as sessions will be
Expand All @@ -43,8 +45,9 @@ func NewAuthService(ctx context.Context) (*AuthService, error) {
}

return &AuthService{
aes32Key: key,
Cache: NewCache(ctx, model.SessionDuration),
aes32Key: key,
Cache: NewCache(ctx, model.SessionDuration),
IsDemoMode: isDemoMode,
}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion core/util/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func SetupAuthTest(t *testing.T) (*assert.Assertions, *require.Assertions, conte
require := require.New(t)
ctx := context.Background()

auth, err := util.NewAuthService(ctx)
auth, err := util.NewAuthService(ctx, false)
require.NoError(err)
assert.NotNil(auth)

Expand Down
6 changes: 5 additions & 1 deletion dashboard/app/api/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,8 +789,8 @@ export interface operations {
};
400: components["responses"]["BadRequestError"];
401: components["responses"]["UnauthorisedError"];
403: components["responses"]["ForbiddenError"];
404: components["responses"]["NotFoundError"];
409: components["responses"]["ConflictError"];
500: components["responses"]["InternalServerError"];
};
};
Expand Down Expand Up @@ -819,6 +819,7 @@ export interface operations {
};
400: components["responses"]["BadRequestError"];
401: components["responses"]["UnauthorisedError"];
403: components["responses"]["ForbiddenError"];
404: components["responses"]["NotFoundError"];
409: components["responses"]["ConflictError"];
500: components["responses"]["InternalServerError"];
Expand Down Expand Up @@ -869,6 +870,7 @@ export interface operations {
};
400: components["responses"]["BadRequestError"];
401: components["responses"]["UnauthorisedError"];
403: components["responses"]["ForbiddenError"];
409: components["responses"]["ConflictError"];
500: components["responses"]["InternalServerError"];
};
Expand Down Expand Up @@ -919,6 +921,7 @@ export interface operations {
};
400: components["responses"]["BadRequestError"];
401: components["responses"]["UnauthorisedError"];
403: components["responses"]["ForbiddenError"];
404: components["responses"]["NotFoundError"];
500: components["responses"]["InternalServerError"];
};
Expand Down Expand Up @@ -951,6 +954,7 @@ export interface operations {
};
400: components["responses"]["BadRequestError"];
401: components["responses"]["UnauthorisedError"];
403: components["responses"]["ForbiddenError"];
404: components["responses"]["NotFoundError"];
500: components["responses"]["InternalServerError"];
};
Expand Down
Loading

0 comments on commit e2595f0

Please sign in to comment.