Skip to content

Commit 3dcdb38

Browse files
author
Laurie T. Malau
committed
[public api] personal access token db model
1 parent fb29551 commit 3dcdb38

File tree

5 files changed

+205
-0
lines changed

5 files changed

+205
-0
lines changed
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package dbtest
6+
7+
import (
8+
"sync"
9+
"testing"
10+
11+
common_db "github.com/gitpod-io/gitpod/common-go/db"
12+
"github.com/stretchr/testify/require"
13+
"gorm.io/gorm"
14+
)
15+
16+
var (
17+
connLock = sync.Mutex{}
18+
conn *gorm.DB
19+
)
20+
21+
func ConnectForTests(t *testing.T) *gorm.DB {
22+
t.Helper()
23+
24+
connLock.Lock()
25+
defer connLock.Unlock()
26+
27+
if conn != nil {
28+
return conn
29+
}
30+
31+
// These are static connection details for tests, started by `leeway components/usage:init-testdb`.
32+
// We use the same static credentials for CI & local instance of MySQL Server.
33+
var err error
34+
conn, err = common_db.Connect(common_db.ConnectionParams{
35+
User: "root",
36+
Password: "test",
37+
Host: "localhost:23306",
38+
Database: "gitpod",
39+
})
40+
require.NoError(t, err, "Failed to establish connection to DB. In a workspace, run `leeway build components/usage:init-testdb` once to bootstrap the DB.")
41+
42+
return conn
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package dbtest
6+
7+
import (
8+
"context"
9+
"database/sql/driver"
10+
"errors"
11+
"fmt"
12+
"strings"
13+
"time"
14+
15+
"github.com/google/uuid"
16+
"gorm.io/gorm"
17+
)
18+
19+
type PersonalAccessToken struct {
20+
ID uuid.UUID `gorm:"primary_key;column:id;type:varchar;size:255;" json:"id"`
21+
UserID uuid.UUID `gorm:"column:userId;type:varchar;size:255;" json:"userId"`
22+
Hash string `gorm:"column:hash;type:varchar;size:255;" json:"hash"`
23+
Name string `gorm:"column:name;type:varchar;size:255;" json:"name"`
24+
Description string `gorm:"column:description;type:varchar;size:255;" json:"description"`
25+
Scopes Scopes `gorm:"column:scopes;type:text;size:65535;" json:"scopes"`
26+
ExpirationTime time.Time `gorm:"column:expirationTime;type:timestamp;" json:"expirationTime"`
27+
CreatedAt time.Time `gorm:"column:createdAt;type:timestamp;default:CURRENT_TIMESTAMP(6);" json:"createdAt"`
28+
LastModified time.Time `gorm:"column:_lastModified;type:timestamp;default:CURRENT_TIMESTAMP(6);" json:"_lastModified"`
29+
30+
// deleted is reserved for use by db-sync.
31+
_ bool `gorm:"column:deleted;type:tinyint;default:0;" json:"deleted"`
32+
}
33+
34+
type Scopes []string
35+
36+
// TableName sets the insert table name for this struct type
37+
func (d *PersonalAccessToken) TableName() string {
38+
return "d_b_personal_access_token"
39+
}
40+
41+
func GetToken(ctx context.Context, conn *gorm.DB, id uuid.UUID) (PersonalAccessToken, error) {
42+
var token PersonalAccessToken
43+
44+
db := conn.WithContext(ctx)
45+
46+
db = db.Where("id = ?", id).First(&token)
47+
if db.Error != nil {
48+
return PersonalAccessToken{}, fmt.Errorf("Failed to retrieve token: %w", db.Error)
49+
}
50+
51+
return token, nil
52+
}
53+
54+
func CreateToken(ctx context.Context, conn *gorm.DB, req PersonalAccessToken) (PersonalAccessToken, error) {
55+
if req.UserID == uuid.Nil {
56+
return PersonalAccessToken{}, fmt.Errorf("Invalid or empty userID")
57+
}
58+
if req.Hash == "" {
59+
return PersonalAccessToken{}, fmt.Errorf("Token hash required")
60+
}
61+
if req.Name == "" {
62+
return PersonalAccessToken{}, fmt.Errorf("Token name required")
63+
}
64+
if req.ExpirationTime.IsZero() {
65+
return PersonalAccessToken{}, fmt.Errorf("Expiration time required")
66+
}
67+
68+
token := PersonalAccessToken{
69+
ID: req.ID,
70+
UserID: req.UserID,
71+
Hash: req.Hash,
72+
Name: req.Name,
73+
Description: req.Description,
74+
Scopes: req.Scopes,
75+
ExpirationTime: req.ExpirationTime,
76+
CreatedAt: time.Now().UTC(),
77+
LastModified: time.Now().UTC(),
78+
}
79+
80+
db := conn.WithContext(ctx).Create(req)
81+
if db.Error != nil {
82+
return PersonalAccessToken{}, fmt.Errorf("Failed to create token for user %s", req.UserID)
83+
}
84+
85+
return token, nil
86+
}
87+
88+
// Scan() and Value() allow having a list of strings as a type for Scopes
89+
func (s *Scopes) Scan(src any) error {
90+
bytes, ok := src.([]byte)
91+
if !ok {
92+
return errors.New("src value cannot cast to []byte")
93+
}
94+
*s = strings.Split(string(bytes), ",")
95+
return nil
96+
}
97+
func (s Scopes) Value() (driver.Value, error) {
98+
if len(s) == 0 {
99+
return nil, nil
100+
}
101+
return strings.Join(s, ","), nil
102+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package dbtest
6+
7+
import (
8+
"context"
9+
"testing"
10+
"time"
11+
12+
"github.com/google/uuid"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestPersonalAccessToken_Get(t *testing.T) {
17+
conn := ConnectForTests(t)
18+
19+
token := PersonalAccessToken{
20+
ID: uuid.New(),
21+
UserID: uuid.New(),
22+
Hash: "some-secure-hash",
23+
Name: "some-name",
24+
Description: "some-description",
25+
Scopes: []string{"read", "write"},
26+
ExpirationTime: time.Now().Add(5),
27+
CreatedAt: time.Now(),
28+
LastModified: time.Now(),
29+
}
30+
31+
tx := conn.Create(token)
32+
require.NoError(t, tx.Error)
33+
34+
result, err := GetToken(context.Background(), conn, token.ID)
35+
require.NoError(t, err)
36+
require.Equal(t, token.ID, result.ID)
37+
}
38+
39+
func TestPersonalAccessToken_Create(t *testing.T) {
40+
conn := ConnectForTests(t)
41+
42+
request := PersonalAccessToken{
43+
ID: uuid.New(),
44+
UserID: uuid.New(),
45+
Hash: "another-secure-hash",
46+
Name: "another-name",
47+
Description: "another-description",
48+
Scopes: []string{"read", "write"},
49+
ExpirationTime: time.Now().Add(5),
50+
CreatedAt: time.Now(),
51+
LastModified: time.Now(),
52+
}
53+
54+
result, err := CreateToken(context.Background(), conn, request)
55+
require.NoError(t, err)
56+
57+
require.Equal(t, request.ID, result.ID)
58+
}

components/common-go/go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ require (
3636

3737
require (
3838
github.com/go-sql-driver/mysql v1.6.0
39+
github.com/google/uuid v1.1.2
3940
github.com/relvacode/iso8601 v1.1.0
4041
gorm.io/driver/mysql v1.4.4
4142
gorm.io/gorm v1.24.1

components/common-go/go.sum

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)