Skip to content

Commit 8360c2d

Browse files
authored
Support Authgear Once user provision #4937
ref DEV-2341
2 parents 6fc7158 + 12cdeec commit 8360c2d

File tree

5 files changed

+222
-59
lines changed

5 files changed

+222
-59
lines changed

Makefile

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,56 @@
1+
# The use of variables
2+
#
3+
# We use simply expanded variables in this Makefile.
4+
#
5+
# This means
6+
# 1. You use ::= instead of = because = defines a recursively expanded variable.
7+
# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html
8+
# 2. You use ::= instead of := because ::= is a POSIX standard.
9+
# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html
10+
# 3. You do not use ?= because it is shorthand to define a recursively expanded variable.
11+
# See https://www.gnu.org/software/make/manual/html_node/Conditional-Assignment.html
12+
# You should use the long form documented in the above link instead.
13+
# 4. When you override a variable in the command line, as documented in https://www.gnu.org/software/make/manual/html_node/Overriding.html
14+
# you specify the variable with ::= instead of = or :=
15+
# If you fail to do so, the variable becomes recursively expanded variable accidentally.
16+
#
117
# GIT_NAME could be empty.
2-
GIT_NAME ?= $(shell git describe --exact-match 2>/dev/null)
3-
GIT_HASH ?= git-$(shell git rev-parse --short=12 HEAD)
18+
ifeq ($(origin GIT_NAME), undefined)
19+
GIT_NAME ::= $(shell git describe --exact-match 2>/dev/null)
20+
endif
21+
ifeq ($(origin GIT_HASH), undefined)
22+
GIT_HASH ::= git-$(shell git rev-parse --short=12 HEAD)
23+
endif
24+
ifeq ($(origin LDFLAGS), undefined)
25+
LDFLAGS ::= "-X github.com/authgear/authgear-server/pkg/version.Version=${GIT_HASH}"
26+
endif
27+
28+
# osusergo: https://godoc.org/github.com/golang/go/src/os/user
29+
# netgo: https://golang.org/doc/go1.5#net
30+
# static_build: https://github.com/golang/go/issues/26492#issuecomment-635563222
31+
# The binary is static on Linux only. It is not static on macOS.
32+
# timetzdata: https://golang.org/doc/go1.15#time/tzdata
33+
GO_BUILD_TAGS ::= osusergo netgo static_build timetzdata
34+
GO_RUN_TAGS ::=
435

5-
LDFLAGS ?= "-X github.com/authgear/authgear-server/pkg/version.Version=${GIT_HASH}"
636

737
.PHONY: start
838
start:
9-
go run -ldflags ${LDFLAGS} ./cmd/authgear start
39+
go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/authgear start
1040

1141
.PHONY: start-portal
1242
start-portal:
13-
go run -ldflags ${LDFLAGS} ./cmd/portal start
43+
go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/portal start
44+
45+
.PHONY: authgearonce-start
46+
authgearonce-start: GO_RUN_TAGS += authgearonce
47+
authgearonce-start:
48+
$(MAKE) start GO_RUN_TAGS::="$(GO_RUN_TAGS)"
49+
50+
.PHONY: authgearonce-start-portal
51+
authgearonce-start-portal: GO_RUN_TAGS += authgearonce
52+
authgearonce-start-portal:
53+
$(MAKE) start-portal GO_RUN_TAGS::="$(GO_RUN_TAGS)"
1454

1555
.PHONY: vendor
1656
vendor:
@@ -88,21 +128,17 @@ fmt:
88128
govulncheck:
89129
govulncheck -show traces,version,verbose ./...
90130

91-
# osusergo: https://godoc.org/github.com/golang/go/src/os/user
92-
# netgo: https://golang.org/doc/go1.5#net
93-
# static_build: https://github.com/golang/go/issues/26492#issuecomment-635563222
94-
# The binary is static on Linux only. It is not static on macOS.
95-
# timetzdata: https://golang.org/doc/go1.15#time/tzdata
96131
.PHONY: build
97132
build:
98-
go build -o $(BIN_NAME) -tags "osusergo netgo static_build timetzdata $(GO_BUILD_TAGS)" -ldflags ${LDFLAGS} ./cmd/$(TARGET)
133+
go build -o $(BIN_NAME) -tags "$(GO_BUILD_TAGS)" -ldflags ${LDFLAGS} ./cmd/$(TARGET)
99134

100135
.PHONY: binary
136+
binary: GO_BUILD_TAGS += authgearlite
101137
binary:
102138
rm -rf ./dist
103139
mkdir ./dist
104-
$(MAKE) build GO_BUILD_TAGS=authgearlite TARGET=authgear BIN_NAME=./dist/authgear-lite-"$(shell go env GOOS)"-"$(shell go env GOARCH)"-${GIT_HASH}
105-
$(MAKE) build GO_BUILD_TAGS=authgearlite TARGET=portal BIN_NAME=./dist/authgear-portal-lite-"$(shell go env GOOS)"-"$(shell go env GOARCH)"-${GIT_HASH}
140+
$(MAKE) build GO_BUILD_TAGS::="$(GO_BUILD_TAGS)" TARGET::=authgear BIN_NAME::=./dist/authgear-lite-"$(shell go env GOOS)"-"$(shell go env GOARCH)"-${GIT_HASH}
141+
$(MAKE) build GO_BUILD_TAGS::="$(GO_BUILD_TAGS)" TARGET::=portal BIN_NAME::=./dist/authgear-portal-lite-"$(shell go env GOOS)"-"$(shell go env GOARCH)"-${GIT_HASH}
106142

107143
.PHONY: check-tidy
108144
check-tidy:
@@ -130,14 +166,14 @@ build-image:
130166
docker build --pull --file ./cmd/$(TARGET)/Dockerfile --tag $(IMAGE_NAME) --build-arg GIT_HASH=$(GIT_HASH) .
131167

132168
.PHONY: tag-image
133-
tag-image: DOCKER_IMAGE = quay.io/theauthgear/$(IMAGE_NAME)
169+
tag-image: DOCKER_IMAGE ::= quay.io/theauthgear/$(IMAGE_NAME)
134170
tag-image:
135171
docker tag $(IMAGE_NAME) $(DOCKER_IMAGE):latest
136172
docker tag $(IMAGE_NAME) $(DOCKER_IMAGE):$(GIT_HASH)
137173
if [ ! -z $(GIT_NAME) ]; then docker tag $(IMAGE_NAME) $(DOCKER_IMAGE):$(GIT_NAME); fi
138174

139175
.PHONY: push-image
140-
push-image: DOCKER_IMAGE = quay.io/theauthgear/$(IMAGE_NAME)
176+
push-image: DOCKER_IMAGE ::= quay.io/theauthgear/$(IMAGE_NAME)
141177
push-image:
142178
docker manifest inspect $(DOCKER_IMAGE):$(GIT_HASH) > /dev/null; if [ $$? -eq 0 ]; then \
143179
echo "$(DOCKER_IMAGE):$(GIT_HASH) exists. Skip push"; \

custombuild/Makefile

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,49 @@
1+
# The use of variables
2+
#
3+
# We use simply expanded variables in this Makefile.
4+
#
5+
# This means
6+
# 1. You use ::= instead of = because = defines a recursively expanded variable.
7+
# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html
8+
# 2. You use ::= instead of := because ::= is a POSIX standard.
9+
# See https://www.gnu.org/software/make/manual/html_node/Simple-Assignment.html
10+
# 3. You do not use ?= because it is shorthand to define a recursively expanded variable.
11+
# See https://www.gnu.org/software/make/manual/html_node/Conditional-Assignment.html
12+
# You should use the long form documented in the above link instead.
13+
# 4. When you override a variable in the command line, as documented in https://www.gnu.org/software/make/manual/html_node/Overriding.html
14+
# you specify the variable with ::= instead of = or :=
15+
# If you fail to do so, the variable becomes recursively expanded variable accidentally.
16+
#
117
# GIT_NAME could be empty.
2-
GIT_NAME ?= $(shell git describe --exact-match 2>/dev/null)
3-
GIT_HASH ?= git-$(shell git rev-parse --short=12 HEAD)
18+
ifeq ($(origin GIT_NAME), undefined)
19+
GIT_NAME ::= $(shell git describe --exact-match 2>/dev/null)
20+
endif
21+
ifeq ($(origin GIT_HASH), undefined)
22+
GIT_HASH ::= git-$(shell git rev-parse --short=12 HEAD)
23+
endif
24+
ifeq ($(origin LDFLAGS), undefined)
25+
LDFLAGS ::= "-X github.com/authgear/authgear-server/pkg/version.Version=${GIT_HASH}"
26+
endif
427

5-
LDFLAGS ?= "-X github.com/authgear/authgear-server/pkg/version.Version=${GIT_HASH}"
28+
# osusergo: https://godoc.org/github.com/golang/go/src/os/user
29+
# netgo: https://golang.org/doc/go1.5#net
30+
# static_build: https://github.com/golang/go/issues/26492#issuecomment-635563222
31+
# The binary is static on Linux only. It is not static on macOS.
32+
# timetzdata: https://golang.org/doc/go1.15#time/tzdata
33+
GO_BUILD_TAGS ::= osusergo netgo static_build timetzdata
34+
GO_RUN_TAGS ::=
635

736
.PHONY: start
837
start:
9-
go run -ldflags ${LDFLAGS} ./cmd/authgearx start
38+
go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/authgearx start
1039

1140
.PHONY: start-portal
1241
start-portal:
13-
go run -ldflags ${LDFLAGS} ./cmd/portalx start
42+
go run -tags "$(GO_RUN_TAGS)" -ldflags ${LDFLAGS} ./cmd/portalx start
1443

1544
.PHONY: build
1645
build:
17-
go build -o $(BIN_NAME) -tags "osusergo netgo static_build timetzdata $(GO_BUILD_TAGS)" -ldflags ${LDFLAGS} ./cmd/$(TARGET)
46+
go build -o $(BIN_NAME) -tags "$(GO_BUILD_TAGS)" -ldflags ${LDFLAGS} ./cmd/$(TARGET)
1847

1948
.PHONY: build-image
2049
build-image:

pkg/portal/service/collaborator.go

Lines changed: 124 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -465,11 +465,10 @@ func (s *CollaboratorService) SendInvitation(
465465
return nil, err
466466
}
467467

468-
// TODO(collaborator): Ideally we should prevent sending invitation to existing collaborator.
468+
// Ideally we should prevent sending invitation to existing collaborator.
469469
// However, this is not harmful to not have it.
470470
// The collaborator will receive the invitation and they cannot accept it because
471471
// we have database constraint to enforce this invariant.
472-
// If Admin API have getUserByClaim, then we can detect this condition here.
473472

474473
// Check if the invitee has a pending invitation already.
475474
invitations, err := s.ListInvitations(ctx, appID)
@@ -482,6 +481,20 @@ func (s *CollaboratorService) SendInvitation(
482481
}
483482
}
484483

484+
if AUTHGEARONCE {
485+
inviteeExists, err := s.checkInviteeExistenceByEmail(ctx, invitedBy, inviteeEmail)
486+
if err != nil {
487+
return nil, err
488+
}
489+
490+
if !inviteeExists {
491+
err = s.createAccountForInvitee(ctx, invitedBy, inviteeEmail)
492+
if err != nil {
493+
return nil, err
494+
}
495+
}
496+
}
497+
485498
code := generateCollaboratorInvitationCode()
486499
now := s.Clock.NowUTC()
487500
// Expire in 3 days.
@@ -748,22 +761,19 @@ func (s *CollaboratorService) CheckInviteeEmail(ctx context.Context, i *model.Co
748761
id := relay.ToGlobalID("User", actorID)
749762

750763
params := graphqlutil.DoParams{
751-
OperationName: "getUserNodes",
764+
OperationName: "getUserNode",
752765
Query: `
753-
query getUserNodes($ids: [ID!]!) {
754-
nodes(ids: $ids) {
766+
query getUserNode($id: ID!) {
767+
node(id: $id) {
755768
... on User {
756769
id
757-
verifiedClaims {
758-
name
759-
value
760-
}
770+
standardAttributes
761771
}
762772
}
763773
}
764774
`,
765775
Variables: map[string]interface{}{
766-
"ids": []interface{}{id},
776+
"id": id,
767777
},
768778
}
769779

@@ -788,45 +798,121 @@ func (s *CollaboratorService) CheckInviteeEmail(ctx context.Context, i *model.Co
788798
return fmt.Errorf("unexpected graphql errors: %v", result.Errors)
789799
}
790800

791-
var userModels []*model.User
801+
var email string
792802
data := result.Data.(map[string]interface{})
793-
nodes := data["nodes"].([]interface{})
794-
for _, iface := range nodes {
795-
// It could be null.
796-
userNode, ok := iface.(map[string]interface{})
797-
if !ok {
798-
userModels = append(userModels, nil)
799-
} else {
800-
userModel := &model.User{}
801-
globalID := userNode["id"].(string)
802-
userModel.ID = globalID
803-
804-
// Use the last email claim.
805-
verifiedClaims := userNode["verifiedClaims"].([]interface{})
806-
for _, iface := range verifiedClaims {
807-
claim := iface.(map[string]interface{})
808-
name := claim["name"].(string)
809-
value := claim["value"].(string)
810-
if name == "email" {
811-
userModel.Email = value
812-
}
803+
if userNode, ok := data["node"].(map[string]interface{}); ok {
804+
if standardAttributes, ok := userNode["standardAttributes"].(map[string]interface{}); ok {
805+
if e, ok := standardAttributes["email"].(string); ok {
806+
email = e
813807
}
808+
}
809+
}
810+
811+
if email != i.InviteeEmail {
812+
return ErrCollaboratorInvitationInvalidEmail
813+
}
814+
815+
return nil
816+
}
814817

815-
userModels = append(userModels, userModel)
818+
// checkInviteeExistenceByEmail calls HTTP request.
819+
func (s *CollaboratorService) checkInviteeExistenceByEmail(ctx context.Context, actorUserID string, inviteeEmail string) (inviteeExists bool, err error) {
820+
params := graphqlutil.DoParams{
821+
OperationName: "getUsersByStandardAttribute",
822+
Query: `
823+
query getUsersByStandardAttribute($name: String!, $value: String!) {
824+
users: getUsersByStandardAttribute(attributeName: $name, attributeValue: $value) {
825+
id
826+
}
816827
}
828+
`,
829+
Variables: map[string]interface{}{
830+
"name": "email",
831+
"value": inviteeEmail,
832+
},
817833
}
818834

819-
if len(userModels) != 1 {
820-
return fmt.Errorf("expected exact one user")
835+
r, err := http.NewRequestWithContext(ctx, "POST", "/graphql", nil)
836+
if err != nil {
837+
return
821838
}
822839

823-
user := userModels[0]
840+
director, err := s.AdminAPI.SelfDirector(ctx, actorUserID, UsageInternal)
841+
if err != nil {
842+
return
843+
}
824844

825-
if user.Email != i.InviteeEmail {
826-
return ErrCollaboratorInvitationInvalidEmail
845+
director(r)
846+
847+
result, err := graphqlutil.HTTPDo(s.HTTPClient.Client, r, params)
848+
if err != nil {
849+
return
827850
}
828851

829-
return nil
852+
if result.HasErrors() {
853+
err = fmt.Errorf("unexpected graphql errors: %v", result.Errors)
854+
return
855+
}
856+
857+
data := result.Data.(map[string]interface{})
858+
users := data["users"].([]interface{})
859+
if len(users) > 0 {
860+
inviteeExists = true
861+
return
862+
}
863+
864+
return
865+
}
866+
867+
// createAccountForInvitee calls HTTP request.
868+
func (s *CollaboratorService) createAccountForInvitee(ctx context.Context, actorUserID string, inviteeEmail string) (err error) {
869+
params := graphqlutil.DoParams{
870+
OperationName: "createAccount",
871+
Query: `
872+
mutation createAccount($email: String!) {
873+
createUser(input: {
874+
definition: {
875+
loginID: {
876+
key: "email"
877+
value: $email
878+
}
879+
}
880+
sendPassword: true
881+
setPasswordExpired: true
882+
}) {
883+
user {
884+
id
885+
}
886+
}
887+
}
888+
`,
889+
Variables: map[string]interface{}{
890+
"email": inviteeEmail,
891+
},
892+
}
893+
894+
r, err := http.NewRequestWithContext(ctx, "POST", "/graphql", nil)
895+
if err != nil {
896+
return err
897+
}
898+
899+
director, err := s.AdminAPI.SelfDirector(ctx, actorUserID, UsageInternal)
900+
if err != nil {
901+
return err
902+
}
903+
904+
director(r)
905+
906+
result, err := graphqlutil.HTTPDo(s.HTTPClient.Client, r, params)
907+
if err != nil {
908+
return err
909+
}
910+
911+
if result.HasErrors() {
912+
return fmt.Errorf("unexpected graphql errors: %v", result.Errors)
913+
}
914+
915+
return
830916
}
831917

832918
func (s *CollaboratorService) checkQuotaInSend(ctx context.Context, appID string) error {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//go:build authgearonce
2+
// +build authgearonce
3+
4+
package service
5+
6+
const AUTHGEARONCE = true
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//go:build !authgearonce
2+
// +build !authgearonce
3+
4+
package service
5+
6+
const AUTHGEARONCE = false

0 commit comments

Comments
 (0)