@@ -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
832918func (s * CollaboratorService ) checkQuotaInSend (ctx context.Context , appID string ) error {
0 commit comments