Architecture
Components related to the "Allow users to update their login and email addresses"
Schema
ws.sys.UserProfileWS.UserProfile
ALTERABLE WORKSPACE UserProfileWS INHERITS sys.ProfileWS (
DESCRIPTOR UserProfile (
DisplayName varchar,
);
...
cdoc.registry.AppWorkspaceWS.Login
ALTER WORKSPACE sys.AppWorkspaceWS (
TABLE Login INHERITS sys.CDoc (
ProfileCluster int32 NOT NULL,
PwdHash bytes NOT NULL,
AppName varchar NOT NULL,
SubjectKind int32,
LoginHash varchar NOT NULL,
WSID int64, -- to be written after workspace init
WSError varchar(1024), -- to be written after workspace init
WSKindInitializationData varchar(1024) NOT NULL
);
TABLE Invite INHERITS sys.CDoc (
SubjectKind int32,
Login varchar NOT NULL, <--
Email varchar NOT NULL,
Roles varchar(1024),
ExpireDatetime int64,
VerificationCode varchar,
State int32 NOT NULL,
Created int64,
Updated int64 NOT NULL,
SubjectID ref,
InviteeProfileWSID int64,
ActualLogin varchar,
UNIQUEFIELD Email
) WITH Tags=(WorkspaceOwnerTableTag);
TABLE Subject INHERITS sys.CDoc (
Login varchar NOT NULL, <--
SubjectKind int32 NOT NULL,
Roles varchar(1024) NOT NULL,
ProfileWSID int64 NOT NULL,
UNIQUEFIELD Login
) WITH Tags=(WorkspaceOwnerTableTag);
VIEW ViewSubjectsIdx (
LoginHash int64 NOT NULL,
Login text NOT NULL,
SubjectID ref NOT NULL,
PRIMARY KEY ((LoginHash), Login)
) AS RESULT OF ApplyViewSubjectsIdx WITH Tags=(WorkspaceOwnerTableTag);
Invite.Email usage
This is the primary place where the Email field is set in the Invite record during the invitation process. The code is in the execCmdInitiateInvitationByEMail function which creates a new invitation:
// Initial creation of the invite record with Email field
svbCDocInvite.PutString(Field_Login, args.ArgumentObject.AsString(field_Email))
svbCDocInvite.PutString(field_Email, args.ArgumentObject.AsString(field_Email))
svbCDocInvite.PutString(Field_Roles, args.ArgumentObject.AsString(Field_Roles))
The email is also used in several places, particularly when sending emails:
pkg\sys\invite\impl_applyupdateinviteroles.go:
// Using the Email field to send notification about role updates
skbSendMail.PutString(sys.Storage_SendMail_Field_Subject, event.ArgumentObject().AsString(field_EmailSubject))
skbSendMail.PutString(sys.Storage_SendMail_Field_To, svCDocInvite.AsString(field_Email))
skbSendMail.PutString(sys.Storage_SendMail_Field_Body, replacer.Replace(emailTemplate))
pkg\sys\invite\impl_applyinvitation.go
// Using the Email field to send invitation email
skbSendMail.PutString(sys.Storage_SendMail_Field_Subject, event.ArgumentObject().AsString(field_EmailSubject))
skbSendMail.PutString(sys.Storage_SendMail_Field_To, event.ArgumentObject().AsString(field_Email))
skbSendMail.PutString(sys.Storage_SendMail_Field_Body, replacer.Replace(emailTemplate))
sys.Storage_SendMail usage
Subject.Login Usage
The Subject.Login field is a critical component in the Voedger authentication and authorization system. This section documents its usage patterns across the codebase and demonstrates how it serves as a unique identifier for subjects within the system.
Overview
The Subject.Login field serves as the primary identifier for subjects and is used extensively throughout the system for:
- Subject Creation and Identification: Creating new subjects and uniquely identifying existing ones
- Query Operations: Retrieving subjects based on their login credentials
- Index Management: Building and maintaining lookup indexes for efficient subject retrieval
- Workspace Association: Linking subjects to their respective profiles and workspaces
Key Usage Patterns
1. Login Field Extraction for Indexing
Location: pkg/sys/invite/impl_applyviewsubjectsidx.go
// Extract login from subject document for index building
actualLogin := cdocSubject.AsString(Field_Login)
// Maps cdoc.sys.Subject.Login <- cdoc.sys.Invite.ActualLogin by ap.sys.ApplyJoinWorkspace
2. Test Helper for Subject Queries
Location: pkg/sys/it/impl_invite_test.go
// Test helper function to query subjects by login
findCDocSubjectByLogin := func(login string) []interface{} {
return vit.PostWS(ws, "q.sys.Collection", fmt.Sprintf(
`{"args":{"Schema":"sys.Subject"},
"elements":[{"fields":[
"Login",
"SubjectKind",
"Roles",
"sys.ID"
]}]}`
))
}
3. Test Subject Creation
Location: pkg/vit/impl.go
// Create test subjects with login field
body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"sys.Subject","Login":"%s","Roles":"%s","SubjectKind":%d,"ProfileWSID":%d}}]}`,
subject.login, roles, subject.subjectKind, vit.principals[appQName][subject.login].ProfileWSID)
4. Subject Role Retrieval by Login
Location: pkg/vvm/provide.go
// Provider function for retrieving subject roles by login
func provideSubjectGetterFunc() iauthnzimpl.SubjectGetterFunc {
return func(requestContext context.Context, name string, as istructs.IAppStructs, wsid istructs.WSID) ([]appdef.QName, error) {
kb := as.ViewRecords().KeyBuilder(invite.QNameViewSubjectsIdx)
kb.PutInt64(invite.Field_LoginHash, coreutils.LoginHash(name))
kb.PutString(invite.Field_Login, name)
subjectsIdx, err := as.ViewRecords().Get(wsid, kb)
if err == istructs.ErrRecordNotFound {
return nil, nil
}
if err != nil {
return nil, err
}
res := []appdef.QName{}
subjectID := subjectsIdx.AsRecordID(invite.Field_SubjectID)
cdocSubject, err := as.Records().Get(wsid, true, istructs.RecordID(subjectID))
if err != nil {
return nil, err
}
if !cdocSubject.AsBool(appdef.SystemField_IsActive) {
return nil, nil
}
roles := strings.Split(cdocSubject.AsString(invite.Field_Roles), ",")
for _, role := range roles {
roleQName, err := appdef.ParseQName(role)
if err != nil {
return nil, err
}
res = append(res, roleQName)
}
return res, nil
}
}
Additional Login usage patterns
5. Login Extraction from Authentication Token
Location: pkg/sys/invite/utils.go
// Extract login from authentication token
func LoginFromToken(st istructs.IState) (loginFromToken string, err error) {
skbPrincipal, err := st.KeyBuilder(sys.Storage_RequestSubject, appdef.NullQName)
if err != nil {
return "", err
}
svPrincipal, err := st.MustExist(skbPrincipal)
if err != nil {
return "", err
}
return svPrincipal.AsString(sys.Storage_RequestSubject_Field_Name), nil
}
6. Subject Creation in Workspace Configuration
Location: pkg/vit/impl_vitcfg.go
// Add subject with specific login to workspace or login configuration
func WithSubject(login string, subjectKind istructs.SubjectKindType, roles []appdef.QName) PostConstructFunc {
return func(intf interface{}) {
switch withSubjects := intf.(type) {
case *WSParams:
withSubjects.subjects = append(withSubjects.subjects, subject{
login: login,
subjectKind: subjectKind,
roles: roles,
})
case *Login:
withSubjects.subjects = append(withSubjects.subjects, subject{
login: login,
subjectKind: subjectKind,
roles: roles,
})
}
}
}
7. Subject Existence Verification
Location: pkg/sys/invite/impl_applyjoinworkspace.go
// Check if subject exists by login before creating new one
login := svCDocInvite.AsString(Field_Login)
subjectExistsByActualLogin := false
existingSubjectID, err := SubjectExistsByLogin(login, s) // for backward compatibility
subjectExistsByLogin := existingSubjectID > 0
if err == nil && !subjectExistsByLogin {
login = svCDocInvite.AsString(field_ActualLogin)
existingSubjectID, err = SubjectExistsByLogin(login, s)
subjectExistsByActualLogin = existingSubjectID > 0
}
8. Device Login Generation
Location: pkg/router/impl_apiv2.go
// Generate random login for device authentication
login, pwd := coreutils.DeviceRandomLoginPwd()
pseudoWSID := coreutils.GetPseudoWSID(istructs.NullWSID, login, istructs.CurrentClusterID())
url := fmt.Sprintf("api/v2/apps/sys/registry/workspaces/%d/commands/registry.CreateLogin", pseudoWSID)
body := fmt.Sprintf(`{"args":{"Login":"%s","AppName":"%s","SubjectKind":%d,"WSKindInitializationData":"{}","ProfileCluster":%d},"unloggedArgs":{"Password":"%s"}}`,
login, busRequest.AppQName, istructs.SubjectKind_Device, istructs.CurrentClusterID(), pwd)
Implementation Details
The Subject.Login field is implemented with the following characteristics:
- Data Type:
varchar(variable-length character string) - Constraints:
NOT NULLandUNIQUEFIELD(ensures uniqueness across the system) - Indexing: Used in
ViewSubjectsIdxfor efficient lookup operations - Hashing: Login values are hashed using
coreutils.LoginHash()for index optimization
Database Schema Definition
The Subject.Login field is defined in the system schema as follows:
TABLE Subject INHERITS sys.CDoc (
Login varchar NOT NULL,
SubjectKind int32 NOT NULL,
Roles varchar(1024) NOT NULL,
ProfileWSID int64 NOT NULL,
UNIQUEFIELD Login
) WITH Tags=(WorkspaceOwnerTableTag);
Index Management Functions
Location: pkg/sys/invite/utils.go
The system provides utility functions for efficient subject lookup operations:
// Build key for subject index lookup
func GetSubjectIdxViewKeyBuilder(login string, s istructs.IState) (istructs.IStateKeyBuilder, error) {
skbViewSubjectsIdx, err := s.KeyBuilder(sys.Storage_View, QNameViewSubjectsIdx)
if err != nil {
return nil, err
}
skbViewSubjectsIdx.PutInt64(Field_LoginHash, coreutils.LoginHash(login))
skbViewSubjectsIdx.PutString(Field_Login, login)
return skbViewSubjectsIdx, nil
}
// Check if subject exists by login
func SubjectExistsByLogin(login string, state istructs.IState) (existingSubjectID istructs.RecordID, err error) {
skbViewSubjectsIdx, err := GetSubjectIdxViewKeyBuilder(login, state)
if err != nil {
return 0, err
}
val, ok, err := state.CanExist(skbViewSubjectsIdx)
if ok {
existingSubjectID = val.AsRecordID("SubjectID")
}
return existingSubjectID, err
}
Supporting View Schema
The system maintains an optimized view for subject lookups:
VIEW ViewSubjectsIdx (
LoginHash int64 NOT NULL,
Login text NOT NULL,
SubjectID ref NOT NULL,
PRIMARY KEY ((LoginHash), Login)
) AS RESULT OF ApplyViewSubjectsIdx WITH Tags=(WorkspaceOwnerTableTag);
This view enables efficient subject retrieval by combining login hashing with direct login string matching, providing both performance optimization and exact match capabilities