Skip to content

Commit

Permalink
feat: Add Invitation Code to Generate Invitation Link (casdoor#2666)
Browse files Browse the repository at this point in the history
Add auto-population of invitation fields in the registration page based on the invitation code in the link
  • Loading branch information
HGZ-20 authored Feb 2, 2024
1 parent bbbda19 commit b7be194
Show file tree
Hide file tree
Showing 56 changed files with 268 additions and 26 deletions.
1 change: 1 addition & 0 deletions authz/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ p, *, *, GET, /api/get-organization-names, *, *
p, *, *, GET, /api/get-all-objects, *, *
p, *, *, GET, /api/get-all-actions, *, *
p, *, *, GET, /api/get-all-roles, *, *
p, *, *, GET, /api/get-invitation-info, *, *
`

sa := stringadapter.NewAdapter(ruleText)
Expand Down
2 changes: 1 addition & 1 deletion controllers/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (c *ApiController) Signup() {

if invitation != nil {
invitation.UsedCount += 1
_, err := object.UpdateInvitation(invitation.GetId(), invitation)
_, err := object.UpdateInvitation(invitation.GetId(), invitation, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
Expand Down
30 changes: 28 additions & 2 deletions controllers/invitation.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,32 @@ func (c *ApiController) GetInvitation() {
c.ResponseOk(invitation)
}

// GetInvitationCodeInfo
// @Title GetInvitationCodeInfo
// @Tag Invitation API
// @Description get invitation code information
// @Param code query string true "Invitation code"
// @Success 200 {object} object.Invitation The Response object
// @router /get-invitation-info [get]
func (c *ApiController) GetInvitationCodeInfo() {
code := c.Input().Get("code")
applicationId := c.Input().Get("applicationId")

application, err := object.GetApplication(applicationId)
if err != nil {
c.ResponseError(err.Error())
return
}

invitation, msg := object.GetInvitationByCode(code, application.Organization, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
return
}

c.ResponseOk(object.GetMaskedInvitation(invitation))
}

// UpdateInvitation
// @Title UpdateInvitation
// @Tag Invitation API
Expand All @@ -102,7 +128,7 @@ func (c *ApiController) UpdateInvitation() {
return
}

c.Data["json"] = wrapActionResponse(object.UpdateInvitation(id, &invitation))
c.Data["json"] = wrapActionResponse(object.UpdateInvitation(id, &invitation, c.GetAcceptLanguage()))
c.ServeJSON()
}

Expand All @@ -121,7 +147,7 @@ func (c *ApiController) AddInvitation() {
return
}

c.Data["json"] = wrapActionResponse(object.AddInvitation(&invitation))
c.Data["json"] = wrapActionResponse(object.AddInvitation(&invitation, c.GetAcceptLanguage()))
c.ServeJSON()
}

Expand Down
1 change: 1 addition & 0 deletions i18n/locales/ar/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/de/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Zugehörigkeit darf nicht leer sein",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "Anzeigename kann nicht leer sein",
"DisplayName is not valid real name": "DisplayName ist kein gültiger Vorname",
"Email already exists": "E-Mail existiert bereits",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/en/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/es/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Afiliación no puede estar en blanco",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "El nombre de visualización no puede estar en blanco",
"DisplayName is not valid real name": "El nombre de pantalla no es un nombre real válido",
"Email already exists": "El correo electrónico ya existe",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/fa/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/fi/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/fr/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation ne peut pas être vide",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "Le nom d'affichage ne peut pas être vide",
"DisplayName is not valid real name": "DisplayName n'est pas un nom réel valide",
"Email already exists": "E-mail déjà existant",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/he/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/id/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Keterkaitan tidak boleh kosong",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "Nama Pengguna tidak boleh kosong",
"DisplayName is not valid real name": "DisplayName bukanlah nama asli yang valid",
"Email already exists": "Email sudah ada",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/it/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/ja/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "所属は空白にできません",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "表示名は空白にできません",
"DisplayName is not valid real name": "表示名は有効な実名ではありません",
"Email already exists": "メールは既に存在します",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/kk/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/ko/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "소속은 비워 둘 수 없습니다",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName는 비어 있을 수 없습니다",
"DisplayName is not valid real name": "DisplayName는 유효한 실제 이름이 아닙니다",
"Email already exists": "이메일이 이미 존재합니다",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/ms/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/nl/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/pl/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/pt/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/ru/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Принадлежность не может быть пустым значением",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "Имя отображения не может быть пустым",
"DisplayName is not valid real name": "DisplayName не является действительным именем",
"Email already exists": "Электронная почта уже существует",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/sv/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/tr/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/uk/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/vi/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "Tình trạng liên kết không thể để trống",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"DisplayName cannot be blank": "Tên hiển thị không thể để trống",
"DisplayName is not valid real name": "DisplayName không phải là tên thật hợp lệ",
"Email already exists": "Email đã tồn tại",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/zh/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"check": {
"Affiliation cannot be blank": "工作单位不可为空",
"Default code does not match the code's matching rules": "邀请码默认值和邀请码规则不匹配",
"DisplayName cannot be blank": "显示名称不可为空",
"DisplayName is not valid real name": "显示名称必须是真实姓名",
"Email already exists": "该邮箱已存在",
Expand Down
9 changes: 9 additions & 0 deletions object/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,15 @@ func CheckInvitationCode(application *Application, organization *Organization, a
}
}

func CheckInvitationDefaultCode(code string, defaultCode string, lang string) error {
if matched, err := util.IsInvitationCodeMatch(code, defaultCode); err != nil {
return err
} else if !matched {
return fmt.Errorf(i18n.Translate(lang, "check:Default code does not match the code's matching rules"))
}
return nil
}

func checkSigninErrorTimes(user *User, lang string) error {
failedSigninLimit, failedSigninFrozenTime, err := GetFailedSigninConfigByUser(user)
if err != nil {
Expand Down
78 changes: 67 additions & 11 deletions object/invitation.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Invitation struct {
Phone string `xorm:"varchar(100)" json:"phone"`

SignupGroup string `xorm:"varchar(100)" json:"signupGroup"`
DefaultCode string `xorm:"varchar(100)" json:"defaultCode"`

State string `xorm:"varchar(100)" json:"state"`
}
Expand Down Expand Up @@ -93,7 +94,45 @@ func GetInvitation(id string) (*Invitation, error) {
return getInvitation(owner, name)
}

func UpdateInvitation(id string, invitation *Invitation) (bool, error) {
func GetInvitationByCode(code string, organizationName string, lang string) (*Invitation, string) {
invitations, err := GetInvitations(organizationName)
if err != nil {
return nil, err.Error()
}
errMsg := ""
for _, invitation := range invitations {
if isValid, msg := invitation.SimpleCheckInvitationCode(code, lang); isValid {
return invitation, msg
} else if msg != "" && errMsg == "" {
errMsg = msg
}
}

if errMsg != "" {
return nil, errMsg
} else {
return nil, i18n.Translate(lang, "check:Invitation code is invalid")
}
}

func GetMaskedInvitation(invitation *Invitation) *Invitation {
if invitation == nil {
return nil
}

invitation.CreatedTime = ""
invitation.UpdatedTime = ""
invitation.Code = "***"
invitation.DefaultCode = "***"
invitation.IsRegexp = false
invitation.Quota = -1
invitation.UsedCount = -1
invitation.SignupGroup = ""

return invitation
}

func UpdateInvitation(id string, invitation *Invitation, lang string) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
if p, err := getInvitation(owner, name); err != nil {
return false, err
Expand All @@ -107,6 +146,11 @@ func UpdateInvitation(id string, invitation *Invitation) (bool, error) {
invitation.IsRegexp = isRegexp
}

err := CheckInvitationDefaultCode(invitation.Code, invitation.DefaultCode, lang)
if err != nil {
return false, err
}

affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(invitation)
if err != nil {
return false, err
Expand All @@ -115,13 +159,18 @@ func UpdateInvitation(id string, invitation *Invitation) (bool, error) {
return affected != 0, nil
}

func AddInvitation(invitation *Invitation) (bool, error) {
func AddInvitation(invitation *Invitation, lang string) (bool, error) {
if isRegexp, err := util.IsRegexp(invitation.Code); err != nil {
return false, err
} else {
invitation.IsRegexp = isRegexp
}

err := CheckInvitationDefaultCode(invitation.Code, invitation.DefaultCode, lang)
if err != nil {
return false, err
}

affected, err := ormer.Engine.Insert(invitation)
if err != nil {
return false, err
Expand All @@ -147,7 +196,7 @@ func VerifyInvitation(id string) (payment *Payment, attachInfo map[string]interf
return nil, nil, fmt.Errorf("the invitation: %s does not exist", id)
}

func (invitation *Invitation) IsInvitationCodeValid(application *Application, invitationCode string, username string, email string, phone string, lang string) (bool, string) {
func (invitation *Invitation) SimpleCheckInvitationCode(invitationCode string, lang string) (bool, string) {
if matched, err := util.IsInvitationCodeMatch(invitation.Code, invitationCode); err != nil {
return false, err.Error()
} else if !matched {
Expand All @@ -160,6 +209,21 @@ func (invitation *Invitation) IsInvitationCodeValid(application *Application, in
if invitation.UsedCount >= invitation.Quota {
return false, i18n.Translate(lang, "check:Invitation code exhausted")
}

// Determine whether the invitation code is in the form of a regular expression other than pure numbers and letters
if invitation.IsRegexp {
user, _ := GetUserByInvitationCode(invitation.Owner, invitationCode)
if user != nil {
return false, i18n.Translate(lang, "check:The invitation code has already been used")
}
}
return true, ""
}

func (invitation *Invitation) IsInvitationCodeValid(application *Application, invitationCode string, username string, email string, phone string, lang string) (bool, string) {
if isValid, msg := invitation.SimpleCheckInvitationCode(invitationCode, lang); !isValid {
return false, msg
}
if application.IsSignupItemRequired("Username") && invitation.Username != "" && invitation.Username != username {
return false, i18n.Translate(lang, "check:Please register using the username corresponding to the invitation code")
}
Expand All @@ -169,13 +233,5 @@ func (invitation *Invitation) IsInvitationCodeValid(application *Application, in
if application.IsSignupItemRequired("Phone") && invitation.Phone != "" && invitation.Phone != phone {
return false, i18n.Translate(lang, "check:Please register using the phone corresponding to the invitation code")
}

// Determine whether the invitation code is in the form of a regular expression other than pure numbers and letters
if invitation.IsRegexp {
user, _ := GetUserByInvitationCode(invitation.Owner, invitationCode)
if user != nil {
return false, i18n.Translate(lang, "check:The invitation code has already been used")
}
}
return true, ""
}
Loading

0 comments on commit b7be194

Please sign in to comment.