mirror of
https://github.com/harness/drone.git
synced 2025-05-31 03:32:44 +00:00
[Standalone] Make Token Expiration Optional & Fix CLI Register + Login (#239)
This commit is contained in:
parent
ef9a0f28d4
commit
c8ce82d197
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/harness/gitness/cli/provide"
|
||||
"github.com/harness/gitness/cli/textui"
|
||||
"github.com/harness/gitness/internal/api/controller/user"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
@ -21,19 +22,25 @@ type loginCommand struct {
|
||||
func (c *loginCommand) run(*kingpin.ParseContext) error {
|
||||
ss := provide.NewSession()
|
||||
|
||||
username, password := textui.Credentials()
|
||||
loginIdentifier, password := textui.Credentials()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ts, err := provide.OpenClient(c.server).Login(ctx, username, password)
|
||||
in := &user.LoginInput{
|
||||
LoginIdentifier: loginIdentifier,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
ts, err := provide.OpenClient(c.server).Login(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ss.
|
||||
SetURI(c.server).
|
||||
SetExpiresAt(ts.Token.ExpiresAt).
|
||||
// login token always has an expiry date
|
||||
SetExpiresAt(*ts.Token.ExpiresAt).
|
||||
SetAccessToken(ts.AccessToken).
|
||||
Store()
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/harness/gitness/cli/provide"
|
||||
"github.com/harness/gitness/cli/session"
|
||||
"github.com/harness/gitness/cli/textui"
|
||||
"github.com/harness/gitness/internal/api/controller/user"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
@ -30,18 +31,26 @@ type registerCommand struct {
|
||||
func (c *registerCommand) run(*kingpin.ParseContext) error {
|
||||
ss := provide.NewSession()
|
||||
|
||||
username, name, email, password := textui.Registration()
|
||||
uid, displayName, email, password := textui.Registration()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ts, err := provide.OpenClient(c.server).Register(ctx, username, name, email, password)
|
||||
input := &user.RegisterInput{
|
||||
UID: uid,
|
||||
Email: email,
|
||||
DisplayName: displayName,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
ts, err := provide.OpenClient(c.server).Register(ctx, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ss.
|
||||
SetURI(c.server).
|
||||
SetExpiresAt(ts.Token.ExpiresAt).
|
||||
// register token always has an expiry date
|
||||
SetExpiresAt(*ts.Token.ExpiresAt).
|
||||
SetAccessToken(ts.AccessToken).
|
||||
Store()
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/drone/funcmap"
|
||||
"github.com/gotidy/ptr"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
@ -38,9 +39,14 @@ func (c *createPATCommand) run(*kingpin.ParseContext) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
var lifeTime *time.Duration
|
||||
if c.lifetimeInS > 0 {
|
||||
lifeTime = ptr.Duration(time.Duration(int64(time.Second) * c.lifetimeInS))
|
||||
}
|
||||
|
||||
in := user.CreateTokenInput{
|
||||
UID: c.uid,
|
||||
Lifetime: time.Duration(int64(time.Second) * c.lifetimeInS),
|
||||
Lifetime: lifeTime,
|
||||
Grants: enum.AccessGrantAll,
|
||||
}
|
||||
|
||||
@ -71,7 +77,7 @@ func registerCreatePAT(app *kingpin.CmdClause) {
|
||||
Required().StringVar(&c.uid)
|
||||
|
||||
cmd.Arg("lifetime", "the lifetime of the token in seconds").
|
||||
Required().Int64Var(&c.lifetimeInS)
|
||||
Int64Var(&c.lifetimeInS)
|
||||
|
||||
cmd.Flag("json", "json encode the output").
|
||||
BoolVar(&c.json)
|
||||
|
@ -14,31 +14,41 @@ import (
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// Registration returns the username, name, email and password from stdin.
|
||||
// Registration returns the userID, displayName, email and password from stdin.
|
||||
func Registration() (string, string, string, string) {
|
||||
return Username(), Name(), Email(), Password()
|
||||
return UserID(), DisplayName(), Email(), Password()
|
||||
}
|
||||
|
||||
// Credentials returns the username and password from stdin.
|
||||
// Credentials returns the login identifier and password from stdin.
|
||||
func Credentials() (string, string) {
|
||||
return Username(), Password()
|
||||
return LoginIdentifier(), Password()
|
||||
}
|
||||
|
||||
// Username returns the username from stdin.
|
||||
func Username() string {
|
||||
// UserID returns the user ID from stdin.
|
||||
func UserID() string {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
fmt.Print("Enter Username: ")
|
||||
username, _ := reader.ReadString('\n')
|
||||
fmt.Print("Enter User ID: ")
|
||||
uid, _ := reader.ReadString('\n')
|
||||
|
||||
return strings.TrimSpace(username)
|
||||
return strings.TrimSpace(uid)
|
||||
}
|
||||
|
||||
// Name returns the name from stdin.
|
||||
func Name() string {
|
||||
// LoginIdentifier returns the login identifier from stdin.
|
||||
func LoginIdentifier() string {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
fmt.Print("Enter Name: ")
|
||||
fmt.Print("Enter User ID or Email: ")
|
||||
id, _ := reader.ReadString('\n')
|
||||
|
||||
return strings.TrimSpace(id)
|
||||
}
|
||||
|
||||
// DisplayName returns the display name from stdin.
|
||||
func DisplayName() string {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
fmt.Print("Enter Display Name: ")
|
||||
name, _ := reader.ReadString('\n')
|
||||
|
||||
return strings.TrimSpace(name)
|
||||
|
@ -59,27 +59,18 @@ func (c *HTTPClient) SetDebug(debug bool) {
|
||||
}
|
||||
|
||||
// Login authenticates the user and returns a JWT token.
|
||||
func (c *HTTPClient) Login(ctx context.Context, username, password string) (*types.TokenResponse, error) {
|
||||
form := &url.Values{}
|
||||
form.Add("username", username)
|
||||
form.Add("password", password)
|
||||
func (c *HTTPClient) Login(ctx context.Context, input *user.LoginInput) (*types.TokenResponse, error) {
|
||||
out := new(types.TokenResponse)
|
||||
uri := fmt.Sprintf("%s/api/v1/login", c.base)
|
||||
err := c.post(ctx, uri, true, form, out)
|
||||
err := c.post(ctx, uri, true, input, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Register registers a new user and returns a JWT token.
|
||||
func (c *HTTPClient) Register(ctx context.Context,
|
||||
username, displayName, email, password string) (*types.TokenResponse, error) {
|
||||
form := &url.Values{}
|
||||
form.Add("username", username)
|
||||
form.Add("displayname", displayName)
|
||||
form.Add("email", email)
|
||||
form.Add("password", password)
|
||||
func (c *HTTPClient) Register(ctx context.Context, input *user.RegisterInput) (*types.TokenResponse, error) {
|
||||
out := new(types.TokenResponse)
|
||||
uri := fmt.Sprintf("%s/api/v1/register", c.base)
|
||||
err := c.post(ctx, uri, true, form, out)
|
||||
err := c.post(ctx, uri, true, input, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
@ -201,12 +192,7 @@ func (c *HTTPClient) stream(ctx context.Context, rawurl, method string, noToken
|
||||
var buf io.ReadWriter
|
||||
if in != nil {
|
||||
buf = &bytes.Buffer{}
|
||||
// if posting form data, encode the form values.
|
||||
if form, ok := in.(*url.Values); ok {
|
||||
if _, err = io.WriteString(buf, form.Encode()); err != nil {
|
||||
log.Err(err).Msg("in stream method")
|
||||
}
|
||||
} else if err = json.NewEncoder(buf).Encode(in); err != nil {
|
||||
if err = json.NewEncoder(buf).Encode(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,10 @@ import (
|
||||
// Client to access the remote APIs.
|
||||
type Client interface {
|
||||
// Login authenticates the user and returns a JWT token.
|
||||
Login(ctx context.Context, username, password string) (*types.TokenResponse, error)
|
||||
Login(ctx context.Context, input *user.LoginInput) (*types.TokenResponse, error)
|
||||
|
||||
// Register registers a new user and returns a JWT token.
|
||||
Register(ctx context.Context, username, name, email, password string) (*types.TokenResponse, error)
|
||||
Register(ctx context.Context, input *user.RegisterInput) (*types.TokenResponse, error)
|
||||
|
||||
// Self returns the currently authenticated user.
|
||||
Self(ctx context.Context) (*types.User, error)
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
|
||||
type CreateTokenInput struct {
|
||||
UID string `json:"uid"`
|
||||
Lifetime time.Duration `json:"lifetime"`
|
||||
Lifetime *time.Duration `json:"lifetime"`
|
||||
Grants enum.AccessGrant `json:"grants"`
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ func (c *Controller) CreateToken(ctx context.Context, session *auth.Session,
|
||||
if err = check.UID(in.UID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = check.TokenLifetime(in.Lifetime); err != nil {
|
||||
if err = check.TokenLifetime(in.Lifetime, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Added to unblock UI - Depending on product decision enforce grants, or remove Grants completely.
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
|
||||
type CreateTokenInput struct {
|
||||
UID string `json:"uid"`
|
||||
Lifetime time.Duration `json:"lifetime"`
|
||||
Lifetime *time.Duration `json:"lifetime"`
|
||||
Grants enum.AccessGrant `json:"grants"`
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ func (c *Controller) CreateAccessToken(ctx context.Context, session *auth.Sessio
|
||||
if err = check.UID(in.UID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = check.TokenLifetime(in.Lifetime); err != nil {
|
||||
if err = check.TokenLifetime(in.Lifetime, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Added to unblock UI - Depending on product decision enforce grants, or remove Grants completely.
|
||||
|
@ -27,12 +27,17 @@ type JWTClaims struct {
|
||||
|
||||
// GenerateJWTForToken generates a jwt for a given token.
|
||||
func GenerateJWTForToken(token *types.Token, secret string) (string, error) {
|
||||
var expiresAt int64
|
||||
if token.ExpiresAt != nil {
|
||||
expiresAt = *token.ExpiresAt
|
||||
}
|
||||
|
||||
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, JWTClaims{
|
||||
jwt.StandardClaims{
|
||||
Issuer: issuer,
|
||||
// times required to be in sec not millisec
|
||||
IssuedAt: token.IssuedAt / 1000,
|
||||
ExpiresAt: token.ExpiresAt / 1000,
|
||||
ExpiresAt: expiresAt / 1000,
|
||||
},
|
||||
token.Type,
|
||||
token.ID,
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
"github.com/harness/gitness/internal/store"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/gotidy/ptr"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -28,14 +30,14 @@ func CreateUserSession(ctx context.Context, tokenStore store.TokenStore,
|
||||
principal,
|
||||
principal,
|
||||
uid,
|
||||
userTokenLifeTime,
|
||||
ptr.Duration(userTokenLifeTime),
|
||||
enum.AccessGrantAll,
|
||||
)
|
||||
}
|
||||
|
||||
func CreatePAT(ctx context.Context, tokenStore store.TokenStore,
|
||||
createdBy *types.Principal, createdFor *types.User,
|
||||
uid string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
uid string, lifetime *time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
return Create(
|
||||
ctx,
|
||||
tokenStore,
|
||||
@ -50,7 +52,7 @@ func CreatePAT(ctx context.Context, tokenStore store.TokenStore,
|
||||
|
||||
func CreateSAT(ctx context.Context, tokenStore store.TokenStore,
|
||||
createdBy *types.Principal, createdFor *types.ServiceAccount,
|
||||
uid string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
uid string, lifetime *time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
return Create(
|
||||
ctx,
|
||||
tokenStore,
|
||||
@ -65,9 +67,13 @@ func CreateSAT(ctx context.Context, tokenStore store.TokenStore,
|
||||
|
||||
func Create(ctx context.Context, tokenStore store.TokenStore,
|
||||
tokenType enum.TokenType, createdBy *types.Principal, createdFor *types.Principal,
|
||||
uid string, lifetime time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
uid string, lifetime *time.Duration, grants enum.AccessGrant) (*types.Token, string, error) {
|
||||
issuedAt := time.Now()
|
||||
expiresAt := issuedAt.Add(lifetime)
|
||||
|
||||
var expiresAt *int64
|
||||
if lifetime != nil {
|
||||
expiresAt = ptr.Int64(issuedAt.Add(*lifetime).UnixMilli())
|
||||
}
|
||||
|
||||
// create db entry first so we get the id.
|
||||
token := types.Token{
|
||||
@ -75,7 +81,7 @@ func Create(ctx context.Context, tokenStore store.TokenStore,
|
||||
UID: uid,
|
||||
PrincipalID: createdFor.ID,
|
||||
IssuedAt: issuedAt.UnixMilli(),
|
||||
ExpiresAt: expiresAt.UnixMilli(),
|
||||
ExpiresAt: expiresAt,
|
||||
Grants: grants,
|
||||
CreatedBy: createdBy.ID,
|
||||
}
|
||||
|
@ -17,11 +17,22 @@ var (
|
||||
ErrTokenLifeTimeOutOfBounds = &ValidationError{
|
||||
"The life time of a token has to be between 1 day and 365 days.",
|
||||
}
|
||||
ErrTokenLifeTimeRequired = &ValidationError{
|
||||
"The life time of a token is required.",
|
||||
}
|
||||
)
|
||||
|
||||
// TokenLifetime returns true if the lifetime is valid for a token.
|
||||
func TokenLifetime(lifetime time.Duration) error {
|
||||
if lifetime < minTokenLifeTime || lifetime > maxTokenLifeTime {
|
||||
func TokenLifetime(lifetime *time.Duration, optional bool) error {
|
||||
if lifetime == nil && !optional {
|
||||
return ErrTokenLifeTimeRequired
|
||||
}
|
||||
|
||||
if lifetime == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if *lifetime < minTokenLifeTime || *lifetime > maxTokenLifeTime {
|
||||
return ErrTokenLifeTimeOutOfBounds
|
||||
}
|
||||
|
||||
|
@ -11,14 +11,16 @@ import (
|
||||
// Represents server side infos stored for tokens we distribute.
|
||||
type Token struct {
|
||||
// TODO: int64 ID doesn't match DB
|
||||
ID int64 `db:"token_id" json:"-"`
|
||||
PrincipalID int64 `db:"token_principal_id" json:"principal_id"`
|
||||
Type enum.TokenType `db:"token_type" json:"type"`
|
||||
UID string `db:"token_uid" json:"uid"`
|
||||
ExpiresAt int64 `db:"token_expires_at" json:"expires_at"`
|
||||
IssuedAt int64 `db:"token_issued_at" json:"issued_at"`
|
||||
Grants enum.AccessGrant `db:"token_grants" json:"grants"`
|
||||
CreatedBy int64 `db:"token_created_by" json:"created_by"`
|
||||
ID int64 `db:"token_id" json:"-"`
|
||||
PrincipalID int64 `db:"token_principal_id" json:"principal_id"`
|
||||
Type enum.TokenType `db:"token_type" json:"type"`
|
||||
UID string `db:"token_uid" json:"uid"`
|
||||
// ExpiresAt is an optional unix time that if specified restricts the validity of a token.
|
||||
ExpiresAt *int64 `db:"token_expires_at" json:"expires_at,omitempty"`
|
||||
// IssuedAt is the unix time at which the token was issued.
|
||||
IssuedAt int64 `db:"token_issued_at" json:"issued_at"`
|
||||
Grants enum.AccessGrant `db:"token_grants" json:"grants"`
|
||||
CreatedBy int64 `db:"token_created_by" json:"created_by"`
|
||||
}
|
||||
|
||||
// TokenResponse is returned as part of token creation for PAT / SAT / User Session.
|
||||
|
Loading…
x
Reference in New Issue
Block a user