diff --git a/server/internal/api/api.go b/server/internal/api/api.go new file mode 100644 index 00000000..60ec6c61 --- /dev/null +++ b/server/internal/api/api.go @@ -0,0 +1,72 @@ +package api + +import ( + "errors" + "strings" + + "github.com/gin-gonic/gin" + "github.com/shroff/phylum/server/internal/app" + "github.com/sirupsen/logrus" +) + +func Setup(r *gin.RouterGroup) { + setupAuthRoutes(r) + r.Use(func(c *gin.Context) { + authHeader := strings.Split(c.GetHeader("Authorization"), " ") + logrus.Info(authHeader) + rersponseObj := gin.H{ + "ERR": "Unauthorized", + } + c.JSON(401, rersponseObj) + }) + setupResourceRoutes(r) +} + +func setupAuthRoutes(r *gin.RouterGroup) { + group := r.Group("/auth") + group.POST("/login", handleLoginRoute) +} + +func setupResourceRoutes(r *gin.RouterGroup) { + group := r.Group("/resources") + group.GET("/:id/metadata") + group.PUT("/:id/metadata") + group.GET("/:id/contents") + group.PUT("/:id/contents") +} + +func handleLoginRoute(c *gin.Context) { + username, ok := c.GetQuery("username") + if !ok { + c.AbortWithStatusJSON(401, gin.H{ + "ERR_CODE": "missing_params", + "ERR_DETAILS": "Missing username input", + }) + return + } + password, ok := c.GetQuery("password") + if !ok { + c.AbortWithStatusJSON(401, gin.H{ + "ERR_CODE": "missing_params", + "ERR_DETAILS": "Missing password input", + }) + return + } + + if token, err := app.Default.CreateAccessToken(username, password); err != nil { + if errors.Is(err, app.ErrInvalidUsernamePassword) { + c.AbortWithStatusJSON(401, gin.H{ + "ERR_CODE": "invalid_username_or_password", + }) + } else { + c.AbortWithStatusJSON(401, gin.H{ + "ERR_DETAILS": err.Error(), + }) + } + } else { + c.JSON(200, gin.H{ + "access_token": token.ID, + "expires": token.Expires.Time.String(), + }) + } +} diff --git a/server/internal/app/auth.go b/server/internal/app/auth.go index 51785db6..9e41c124 100644 --- a/server/internal/app/auth.go +++ b/server/internal/app/auth.go @@ -24,30 +24,30 @@ var accessTokenValiditiy = pgtype.Interval{ var ErrInvalidUsernamePassword = errors.New("invalid username or password") var ErrAccessTokenExpired = errors.New("access token expired") -func (a App) CreateAccessToken(username, password, deviceInfo string) (string, error) { +func (a App) CreateAccessToken(username, password string) (sql.AccessToken, error) { if user, err := a.FindUser(context.Background(), username); err != nil { if err == pgx.ErrNoRows { - return "", ErrInvalidUsernamePassword + return sql.AccessToken{}, ErrInvalidUsernamePassword } logrus.Info(err) - return "", err + return sql.AccessToken{}, err } else { if b, err := cryptutil.VerifyPassword(password, user.PasswordHash); err != nil { logrus.Info(err.Error()) - return "", err + return sql.AccessToken{}, err } else if !b { - return "", ErrInvalidUsernamePassword + return sql.AccessToken{}, ErrInvalidUsernamePassword } } - accessToken := GenerateRandomString(accessTokenLength) - if err := a.Db.Queries().InsertAccessToken(context.Background(), sql.InsertAccessTokenParams{ - ID: accessToken, + if token, err := a.Db.Queries().InsertAccessToken(context.Background(), sql.InsertAccessTokenParams{ + ID: GenerateRandomString(accessTokenLength), Validity: accessTokenValiditiy, Username: username, }); err != nil { - return "", err + return sql.AccessToken{}, err + } else { + return token, nil } - return accessToken, nil } func (a App) VerifyAccessToken(accessToken string) (string, error) { diff --git a/server/internal/app/storage_manager.go b/server/internal/app/storage_manager.go index aac03f44..aea85873 100644 --- a/server/internal/app/storage_manager.go +++ b/server/internal/app/storage_manager.go @@ -8,10 +8,6 @@ import ( "github.com/shroff/phylum/server/internal/storage" ) -type storageManager struct { - db *db.DbHandler -} - func restoreStorageBackends(db *db.DbHandler) (map[string]storage.Storage, error) { backends, err := db.Queries().AllStorageBackends(context.Background()) if err != nil { diff --git a/server/internal/command/serve.go b/server/internal/command/serve.go index 9625771a..32b8eb70 100644 --- a/server/internal/command/serve.go +++ b/server/internal/command/serve.go @@ -7,6 +7,7 @@ import ( "github.com/fvbock/endless" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" + "github.com/shroff/phylum/server/internal/api" "github.com/shroff/phylum/server/internal/app" webdav "github.com/shroff/phylum/server/internal/handler_webdav" "github.com/sirupsen/logrus" @@ -24,6 +25,8 @@ func setupServeCommand() *cobra.Command { webdav.SetupHandler(engine.Group(config.GetString("webdav_prefix")), app.Default) + api.Setup(engine.Group("/api/v1")) + server := endless.NewServer(config.GetString("listen"), engine) server.BeforeBegin = func(addr string) { logrus.Info("Listening on " + addr) diff --git a/server/internal/command/user.go b/server/internal/command/user.go index 5667026a..d02caf77 100644 --- a/server/internal/command/user.go +++ b/server/internal/command/user.go @@ -108,11 +108,12 @@ func setupUserLoginCommand() *cobra.Command { } password := string(bytes) - accessToken, err := app.Default.CreateAccessToken(username, password, "cmd") + accessToken, err := app.Default.CreateAccessToken(username, password) if err != nil { logrus.Fatal(err) } - logrus.Info("Access Token: " + accessToken) + logrus.Info("Access Token: " + accessToken.ID) + logrus.Info(" Valid Until: " + accessToken.Expires.Time.String()) }, } } diff --git a/server/internal/sql/access_tokens.sql.go b/server/internal/sql/access_tokens.sql.go index a797c6f6..ae515168 100644 --- a/server/internal/sql/access_tokens.sql.go +++ b/server/internal/sql/access_tokens.sql.go @@ -27,12 +27,12 @@ func (q *Queries) AccessTokenById(ctx context.Context, id string) (AccessToken, return i, err } -const insertAccessToken = `-- name: InsertAccessToken :exec +const insertAccessToken = `-- name: InsertAccessToken :one INSERT INTO access_tokens( id, created, expires, username ) VALUES( $1::text, NOW(), NOW() + $2::interval, $3::text -) +) RETURNING id, created, expires, username ` type InsertAccessTokenParams struct { @@ -41,7 +41,14 @@ type InsertAccessTokenParams struct { Username string } -func (q *Queries) InsertAccessToken(ctx context.Context, arg InsertAccessTokenParams) error { - _, err := q.db.Exec(ctx, insertAccessToken, arg.ID, arg.Validity, arg.Username) - return err +func (q *Queries) InsertAccessToken(ctx context.Context, arg InsertAccessTokenParams) (AccessToken, error) { + row := q.db.QueryRow(ctx, insertAccessToken, arg.ID, arg.Validity, arg.Username) + var i AccessToken + err := row.Scan( + &i.ID, + &i.Created, + &i.Expires, + &i.Username, + ) + return i, err } diff --git a/server/sql/queries/access_tokens.sql b/server/sql/queries/access_tokens.sql index be17c001..f39b84ae 100644 --- a/server/sql/queries/access_tokens.sql +++ b/server/sql/queries/access_tokens.sql @@ -1,9 +1,9 @@ -- name: AccessTokenById :one SELECT * from access_tokens where id = $1; --- name: InsertAccessToken :exec +-- name: InsertAccessToken :one INSERT INTO access_tokens( id, created, expires, username ) VALUES( @id::text, NOW(), NOW() + @validity::interval, @username::text -); +) RETURNING *;