Skip to content

Commit 92d6561

Browse files
authored
Send invitation link from mesh directory when generating and listing OCM tokens (#3724)
* send invite link on token generation * add invite link when getting token list * add changelog * fix tests! * fix changelog name * fix linter * fix tests
1 parent af4c4c8 commit 92d6561

File tree

5 files changed

+127
-19
lines changed

5 files changed

+127
-19
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Enhancement: Send invitation link from mesh directory
2+
when generating and listing OCM tokens
3+
4+
To enhance user expirience, instead of only sending
5+
the token, we send directly the URL for accepting the
6+
invitation workflow.
7+
8+
https://github.com/cs3org/reva/pull/3724

internal/http/services/sciencemesh/email.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type emailParams struct {
3131
User *userpb.User
3232
Token string
3333
MeshDirectoryURL string
34+
InviteLink string
3435
}
3536

3637
const defaultSubject = `ScienceMesh: {{.User.DisplayName}} wants to collaborate with you`
@@ -39,7 +40,7 @@ const defaultBody = `Hi
3940
4041
{{.User.DisplayName}} ({{.User.Mail}}) wants to start sharing OCM resources with you.
4142
To accept the invite, please visit the following URL:
42-
{{.MeshDirectoryURL}}?token={{.Token}}&providerDomain={{.User.Id.Idp}}
43+
{{.InviteLink}}
4344
4445
Alternatively, you can visit your mesh provider and use the following details:
4546
Token: {{.Token}}
@@ -116,3 +117,19 @@ func (h *tokenHandler) initSubjectTemplate(subjTempl string) error {
116117
h.tplSubj = tpl
117118
return nil
118119
}
120+
121+
func (h *tokenHandler) initInviteLinkTemplate(inviteTempl string) error {
122+
var t string
123+
if inviteTempl == "" {
124+
t = defaultInviteLink
125+
} else {
126+
t = inviteTempl
127+
}
128+
129+
tpl, err := template.New("tpl_invite").Parse(t)
130+
if err != nil {
131+
return err
132+
}
133+
h.tplInviteLink = tpl
134+
return nil
135+
}

internal/http/services/sciencemesh/sciencemesh.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,15 @@ func (s *svc) Close() error {
6262
}
6363

6464
type config struct {
65-
Prefix string `mapstructure:"prefix"`
66-
SMTPCredentials *smtpclient.SMTPCredentials `mapstructure:"smtp_credentials"`
67-
GatewaySvc string `mapstructure:"gatewaysvc"`
68-
MeshDirectoryURL string `mapstructure:"mesh_directory_url"`
69-
ProviderDomain string `mapstructure:"provider_domain"`
70-
SubjectTemplate string `mapstructure:"subject_template"`
71-
BodyTemplatePath string `mapstructure:"body_template_path"`
72-
OCMMountPoint string `mapstructure:"ocm_mount_point"`
65+
Prefix string `mapstructure:"prefix"`
66+
SMTPCredentials *smtpclient.SMTPCredentials `mapstructure:"smtp_credentials"`
67+
GatewaySvc string `mapstructure:"gatewaysvc"`
68+
MeshDirectoryURL string `mapstructure:"mesh_directory_url"`
69+
ProviderDomain string `mapstructure:"provider_domain"`
70+
SubjectTemplate string `mapstructure:"subject_template"`
71+
BodyTemplatePath string `mapstructure:"body_template_path"`
72+
OCMMountPoint string `mapstructure:"ocm_mount_point"`
73+
InviteLinkTemplate string `mapstructure:"invite_link_template"`
7374
}
7475

7576
func (c *config) init() {

internal/http/services/sciencemesh/token.go

+81-6
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import (
2424
"html/template"
2525
"mime"
2626
"net/http"
27+
"strings"
2728

2829
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
30+
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
2931
invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
3032
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
3133
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
@@ -36,13 +38,16 @@ import (
3638
"github.com/cs3org/reva/pkg/smtpclient"
3739
)
3840

41+
const defaultInviteLink = "{{.MeshDirectoryURL}}?token={{.Token}}&providerDomain={{.User.Id.Idp}}"
42+
3943
type tokenHandler struct {
4044
gatewayClient gateway.GatewayAPIClient
4145
smtpCredentials *smtpclient.SMTPCredentials
4246
meshDirectoryURL string
4347

44-
tplSubj *template.Template
45-
tplBody *template.Template
48+
tplSubj *template.Template
49+
tplBody *template.Template
50+
tplInviteLink *template.Template
4651
}
4752

4853
func (h *tokenHandler) init(c *config) error {
@@ -65,7 +70,21 @@ func (h *tokenHandler) init(c *config) error {
6570
if err := h.initBodyTemplate(c.BodyTemplatePath); err != nil {
6671
return err
6772
}
68-
return nil
73+
74+
return h.initInviteLinkTemplate(c.InviteLinkTemplate)
75+
}
76+
77+
type token struct {
78+
Token string `json:"token"`
79+
Description string `json:"description,omitempty"`
80+
Expiration uint64 `json:"expiration,omitempty"`
81+
InviteLink string `json:"invite_link"`
82+
}
83+
84+
type inviteLinkParams struct {
85+
User *userpb.User
86+
Token string
87+
MeshDirectoryURL string
6988
}
7089

7190
// Generate generates an invitation token and if a recipient is specified,
@@ -83,10 +102,11 @@ func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) {
83102
return
84103
}
85104

105+
user := ctxpkg.ContextMustGetUser(ctx)
86106
recipient := query.Get("recipient")
87107
if recipient != "" && h.smtpCredentials != nil {
88108
templObj := &emailParams{
89-
User: ctxpkg.ContextMustGetUser(ctx),
109+
User: user,
90110
Token: token.InviteToken.Token,
91111
MeshDirectoryURL: h.meshDirectoryURL,
92112
}
@@ -96,7 +116,13 @@ func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) {
96116
}
97117
}
98118

99-
if err := json.NewEncoder(w).Encode(token.InviteToken); err != nil {
119+
tknRes, err := h.prepareGenerateTokenResponse(user, token.InviteToken)
120+
if err != nil {
121+
reqres.WriteError(w, r, reqres.APIErrorServerError, "error generating response", err)
122+
return
123+
}
124+
125+
if err := json.NewEncoder(w).Encode(tknRes); err != nil {
100126
reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err)
101127
return
102128
}
@@ -105,6 +131,36 @@ func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) {
105131
w.WriteHeader(http.StatusOK)
106132
}
107133

134+
func (h *tokenHandler) generateInviteLink(user *userpb.User, token *invitepb.InviteToken) (string, error) {
135+
var inviteLink strings.Builder
136+
if err := h.tplInviteLink.Execute(&inviteLink, inviteLinkParams{
137+
User: user,
138+
Token: token.Token,
139+
MeshDirectoryURL: h.meshDirectoryURL,
140+
}); err != nil {
141+
return "", err
142+
}
143+
144+
return inviteLink.String(), nil
145+
}
146+
147+
func (h *tokenHandler) prepareGenerateTokenResponse(user *userpb.User, tkn *invitepb.InviteToken) (*token, error) {
148+
inviteLink, err := h.generateInviteLink(user, tkn)
149+
if err != nil {
150+
return nil, err
151+
}
152+
res := &token{
153+
Token: tkn.Token,
154+
Description: tkn.Description,
155+
InviteLink: inviteLink,
156+
}
157+
if tkn.Expiration != nil {
158+
res.Expiration = tkn.Expiration.Seconds
159+
}
160+
161+
return res, nil
162+
}
163+
108164
type acceptInviteRequest struct {
109165
Token string `json:"token"`
110166
ProviderDomain string `json:"providerDomain"`
@@ -221,7 +277,26 @@ func (h *tokenHandler) ListInvite(w http.ResponseWriter, r *http.Request) {
221277
return
222278
}
223279

224-
if err := json.NewEncoder(w).Encode(res.InviteTokens); err != nil {
280+
tokens := make([]*token, 0, len(res.InviteTokens))
281+
user := ctxpkg.ContextMustGetUser(ctx)
282+
for _, tkn := range res.InviteTokens {
283+
inviteURL, err := h.generateInviteLink(user, tkn)
284+
if err != nil {
285+
reqres.WriteError(w, r, reqres.APIErrorServerError, "error generating invite URL from OCM token", err)
286+
return
287+
}
288+
t := &token{
289+
Token: tkn.Token,
290+
Description: tkn.Description,
291+
InviteLink: inviteURL,
292+
}
293+
if tkn.Expiration != nil {
294+
t.Expiration = tkn.Expiration.Seconds
295+
}
296+
tokens = append(tokens, t)
297+
}
298+
299+
if err := json.NewEncoder(w).Encode(tokens); err != nil {
225300
reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling token data", err)
226301
return
227302
}

tests/integration/grpc/ocm_invitation_test.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ import (
4444
"google.golang.org/grpc/metadata"
4545
)
4646

47+
type generateInviteResponse struct {
48+
Token string `json:"token"`
49+
Description string `json:"descriptions"`
50+
Expiration uint64 `json:"expiration"`
51+
InviteLink string `json:"invite_link"`
52+
}
53+
4754
func ctxWithAuthToken(tokenManager token.Manager, user *userpb.User) context.Context {
4855
ctx := context.Background()
4956
scope, err := scope.AddOwnerScope(nil)
@@ -327,7 +334,7 @@ var _ = Describe("ocm invitation workflow", func() {
327334
return users, res.StatusCode
328335
}
329336

330-
generateToken := func(revaToken, domain string) (*invitepb.InviteToken, int) {
337+
generateToken := func(revaToken, domain string) (*generateInviteResponse, int) {
331338
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, fmt.Sprintf("http://%s/sciencemesh/generate-invite", domain), nil)
332339
Expect(err).ToNot(HaveOccurred())
333340
req.Header.Set("x-access-token", revaToken)
@@ -336,9 +343,9 @@ var _ = Describe("ocm invitation workflow", func() {
336343
Expect(err).ToNot(HaveOccurred())
337344
defer res.Body.Close()
338345

339-
var token invitepb.InviteToken
340-
Expect(json.NewDecoder(res.Body).Decode(&token)).To(Succeed())
341-
return &token, res.StatusCode
346+
var inviteRes generateInviteResponse
347+
Expect(json.NewDecoder(res.Body).Decode(&inviteRes)).To(Succeed())
348+
return &inviteRes, res.StatusCode
342349
}
343350

344351
Context("einstein and marie do not know each other", func() {

0 commit comments

Comments
 (0)