feat: sync login email verification support
This commit is contained in:
parent
e0cabb4930
commit
9a501f900a
39 changed files with 2198 additions and 117 deletions
|
|
@ -34,8 +34,9 @@ type tempResolveResult struct {
|
|||
}
|
||||
|
||||
const (
|
||||
authKeyResolveSingleflightPrefix = "resolve:"
|
||||
authClientInfoSingleflightPrefix = "authinfo:"
|
||||
authKeyResolveSingleflightPrefix = "resolve:"
|
||||
authClientInfoSingleflightPrefix = "authinfo:"
|
||||
authKeyClientInfoSingleflightPrefix = "authkeyinfo:"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -154,6 +155,7 @@ type clientSessionInfo struct {
|
|||
layer int
|
||||
clientInfo ClientInfo
|
||||
hasClientInfo bool
|
||||
authKeyInfoChecked bool
|
||||
authorizationChecked bool
|
||||
}
|
||||
|
||||
|
|
@ -226,6 +228,19 @@ func (r *Router) Dispatch(ctx context.Context, authKeyID [8]byte, sessionID int6
|
|||
}
|
||||
tUser := r.clock.Now()
|
||||
info, hasClientMetadata, clientMetadataStored := r.clientSessionInfo(ctx)
|
||||
if authInfo, ok := r.clientSessionInfoFromAuthKey(ctx, effectiveAuthKeyID, info); ok {
|
||||
info = mergeClientSessionInfo(info, authInfo)
|
||||
hasClientMetadata = true
|
||||
r.rememberClientSessionInfo(ctx, info)
|
||||
clientMetadataStored = true
|
||||
if info.layer != 0 {
|
||||
if binder, okBinder := r.deps.Sessions.(ClientLayerBinder); okBinder {
|
||||
if rawAuthKeyID, okRaw := RawAuthKeyIDFrom(ctx); okRaw {
|
||||
binder.SetClientLayerForAuthKey(rawAuthKeyID, sessionID, info.layer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasUserID {
|
||||
if authInfo, ok := r.clientSessionInfoFromAuthorization(ctx, userID, effectiveAuthKeyID, info); ok {
|
||||
info = mergeClientSessionInfo(info, authInfo)
|
||||
|
|
@ -501,6 +516,7 @@ func (r *Router) invalidateAuthUserCache(authKeyID [8]byte) {
|
|||
r.authUserSF.Forget(key)
|
||||
r.authUserSF.Forget(authKeyResolveSingleflightPrefix + key)
|
||||
r.authUserSF.Forget(authClientInfoSingleflightPrefix + key)
|
||||
r.authUserSF.Forget(authKeyClientInfoSingleflightPrefix + key)
|
||||
}
|
||||
|
||||
func (r *Router) scopedSessions() (ScopedSessionBinder, bool) {
|
||||
|
|
@ -619,7 +635,7 @@ func (r *Router) dispatch(ctx context.Context, b *bin.Buffer, depth int) (bin.En
|
|||
if clientDrift {
|
||||
// 客户端漂移只能证明这是 Android 兼容路径;layer 仍以
|
||||
// invokeWithLayer 或授权记录里的真实观测值为准。
|
||||
ctx = r.withAndroidCompatMetadata(ctx)
|
||||
ctx = r.withClientDriftMetadata(ctx, ClientTypeAndroid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -699,6 +715,17 @@ func (r *Router) rememberClientInfo(ctx context.Context, info ClientInfo) {
|
|||
sessionInfo.layer = layer
|
||||
}
|
||||
})
|
||||
r.persistAuthKeyClientInfo(ctx, clientSessionInfo{layer: layer, clientInfo: info, hasClientInfo: true})
|
||||
}
|
||||
|
||||
func (r *Router) rememberClientAPIID(ctx context.Context, apiID int) {
|
||||
if apiID == 0 {
|
||||
return
|
||||
}
|
||||
info := ClientInfo{APIID: apiID, Type: clientTypeFromAPIID(apiID)}
|
||||
sessionInfo := clientSessionInfo{clientInfo: info, hasClientInfo: true}
|
||||
r.rememberClientSessionInfo(ctx, sessionInfo)
|
||||
r.persistAuthKeyClientInfo(ctx, sessionInfo)
|
||||
}
|
||||
|
||||
func (r *Router) rememberClientLayer(ctx context.Context, layer int) {
|
||||
|
|
@ -747,6 +774,7 @@ func (r *Router) rememberClientLayer(ctx context.Context, layer int) {
|
|||
binder.SetClientLayerForAuthKey(rawAuthKeyID, sessionID, layer)
|
||||
}
|
||||
}
|
||||
r.persistAuthKeyClientInfo(ctx, clientSessionInfo{layer: layer})
|
||||
if persistAuthLayer && r.deps.Auth != nil {
|
||||
if err := r.deps.Auth.UpdateAuthorizationLayer(ctx, authKeyID, layer); err != nil {
|
||||
r.log.Warn("update authorization layer failed",
|
||||
|
|
@ -757,6 +785,36 @@ func (r *Router) rememberClientLayer(ctx context.Context, layer int) {
|
|||
}
|
||||
}
|
||||
|
||||
func (r *Router) persistAuthKeyClientInfo(ctx context.Context, info clientSessionInfo) {
|
||||
if r.deps.Auth == nil {
|
||||
return
|
||||
}
|
||||
domainInfo := domainAuthKeyClientInfo(info)
|
||||
if domainInfo.Layer == 0 && domainInfo.DeviceModel == "" && domainInfo.Platform == "" &&
|
||||
domainInfo.SystemVersion == "" && domainInfo.APIID == 0 && domainInfo.AppVersion == "" {
|
||||
return
|
||||
}
|
||||
seen := make(map[[8]byte]struct{}, 2)
|
||||
if rawAuthKeyID, ok := RawAuthKeyIDFrom(ctx); ok && rawAuthKeyID != ([8]byte{}) {
|
||||
seen[rawAuthKeyID] = struct{}{}
|
||||
if err := r.deps.Auth.UpdateAuthKeyClientInfo(ctx, rawAuthKeyID, domainInfo); err != nil {
|
||||
r.log.Warn("update auth key client info failed",
|
||||
zap.String("auth_key_id", fmt.Sprintf("%x", rawAuthKeyID[:])),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
if authKeyID, ok := AuthKeyIDFrom(ctx); ok && authKeyID != ([8]byte{}) {
|
||||
if _, done := seen[authKeyID]; done {
|
||||
return
|
||||
}
|
||||
if err := r.deps.Auth.UpdateAuthKeyClientInfo(ctx, authKeyID, domainInfo); err != nil {
|
||||
r.log.Warn("update auth key client info failed",
|
||||
zap.String("auth_key_id", fmt.Sprintf("%x", authKeyID[:])),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NegotiatedLayer returns the TL layer the given session negotiated via
|
||||
// invokeWithLayer/initConnection. It is keyed first by (auth_key, session) then
|
||||
// falls back to the stable auth_key — so a reconnect with a new session_id still
|
||||
|
|
@ -892,6 +950,9 @@ func clientSessionInfoContains(current, required clientSessionInfo) bool {
|
|||
if required.authorizationChecked && !current.authorizationChecked {
|
||||
return false
|
||||
}
|
||||
if required.authKeyInfoChecked && !current.authKeyInfoChecked {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -982,6 +1043,43 @@ func (r *Router) cachedResolvedAuthClientInfo(authKeyID [8]byte) (clientSessionI
|
|||
return info, true
|
||||
}
|
||||
|
||||
func (r *Router) cachedResolvedAuthKeyClientInfo(authKeyID [8]byte) (clientSessionInfo, bool) {
|
||||
r.clientInfoMu.RLock()
|
||||
defer r.clientInfoMu.RUnlock()
|
||||
info, ok := r.authInfo[authKeyID]
|
||||
if !ok || clientSessionInfoNeedsAuthKeyInfo(info) {
|
||||
return clientSessionInfo{}, false
|
||||
}
|
||||
return info, true
|
||||
}
|
||||
|
||||
func (r *Router) clientSessionInfoFromAuthKey(ctx context.Context, authKeyID [8]byte, current clientSessionInfo) (clientSessionInfo, bool) {
|
||||
if !clientSessionInfoNeedsAuthKeyInfo(current) || r.deps.Auth == nil || authKeyID == ([8]byte{}) {
|
||||
return clientSessionInfo{}, false
|
||||
}
|
||||
v, err, _ := r.authUserSF.Do(authKeyClientInfoSingleflightPrefix+string(authKeyID[:]), func() (any, error) {
|
||||
if cached, ok := r.cachedResolvedAuthKeyClientInfo(authKeyID); ok {
|
||||
return cached, nil
|
||||
}
|
||||
info, found, err := r.deps.Auth.AuthKeyClientInfo(ctx, authKeyID)
|
||||
if err != nil {
|
||||
return clientSessionInfo{}, err
|
||||
}
|
||||
if !found {
|
||||
return clientSessionInfo{authKeyInfoChecked: true}, nil
|
||||
}
|
||||
return clientSessionInfoFromAuthKeyClientInfo(info, current), nil
|
||||
})
|
||||
if err != nil {
|
||||
return clientSessionInfo{}, false
|
||||
}
|
||||
info := v.(clientSessionInfo)
|
||||
if info.layer == 0 && !info.hasClientInfo && !info.authKeyInfoChecked {
|
||||
return clientSessionInfo{}, false
|
||||
}
|
||||
return info, true
|
||||
}
|
||||
|
||||
func mergeClientSessionInfo(base, fallback clientSessionInfo) clientSessionInfo {
|
||||
if base.layer == 0 {
|
||||
base.layer = fallback.layer
|
||||
|
|
@ -993,9 +1091,48 @@ func mergeClientSessionInfo(base, fallback clientSessionInfo) clientSessionInfo
|
|||
if fallback.authorizationChecked {
|
||||
base.authorizationChecked = true
|
||||
}
|
||||
if fallback.authKeyInfoChecked {
|
||||
base.authKeyInfoChecked = true
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func clientSessionInfoFromAuthKeyClientInfo(item domain.AuthKeyClientInfo, current clientSessionInfo) clientSessionInfo {
|
||||
info := clientSessionInfo{
|
||||
layer: item.Layer,
|
||||
authKeyInfoChecked: true,
|
||||
clientInfo: ClientInfo{
|
||||
APIID: item.APIID,
|
||||
DeviceModel: item.DeviceModel,
|
||||
SystemVersion: item.SystemVersion,
|
||||
AppVersion: item.AppVersion,
|
||||
Type: ClientType(item.Platform),
|
||||
},
|
||||
}
|
||||
info.clientInfo = normalizeClientInfo(info.clientInfo)
|
||||
info.hasClientInfo = info.clientInfo.ClientType() != ClientTypeUnknown ||
|
||||
info.clientInfo.DeviceModel != "" ||
|
||||
info.clientInfo.SystemVersion != "" ||
|
||||
info.clientInfo.AppVersion != "" ||
|
||||
info.clientInfo.APIID != 0
|
||||
if info.layer == 0 && current.layer != 0 {
|
||||
info.layer = current.layer
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func domainAuthKeyClientInfo(info clientSessionInfo) domain.AuthKeyClientInfo {
|
||||
out := domain.AuthKeyClientInfo{Layer: info.layer}
|
||||
if info.hasClientInfo {
|
||||
out.APIID = info.clientInfo.APIID
|
||||
out.DeviceModel = info.clientInfo.DeviceModel
|
||||
out.SystemVersion = info.clientInfo.SystemVersion
|
||||
out.AppVersion = info.clientInfo.AppVersion
|
||||
out.Platform = string(info.clientInfo.ClientType())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *Router) clientSessionInfoFromAuthorization(ctx context.Context, userID int64, authKeyID [8]byte, current clientSessionInfo) (clientSessionInfo, bool) {
|
||||
if !clientSessionInfoNeedsAuthorization(current) || r.deps.Auth == nil || userID == 0 {
|
||||
return clientSessionInfo{}, false
|
||||
|
|
@ -1052,6 +1189,13 @@ func clientSessionInfoNeedsAuthorization(info clientSessionInfo) bool {
|
|||
return info.layer == 0 || !info.hasClientInfo || info.clientInfo.ClientType() == ClientTypeUnknown
|
||||
}
|
||||
|
||||
func clientSessionInfoNeedsAuthKeyInfo(info clientSessionInfo) bool {
|
||||
if info.authKeyInfoChecked {
|
||||
return false
|
||||
}
|
||||
return info.layer == 0 || !info.hasClientInfo || info.clientInfo.ClientType() == ClientTypeUnknown
|
||||
}
|
||||
|
||||
// fallback 处理未注册的 RPC:记录到 compatibility trace(落兼容矩阵),
|
||||
// 返回 NOT_IMPLEMENTED rpc_error 让客户端继续运行而非断连。
|
||||
func (r *Router) fallback(ctx context.Context, b *bin.Buffer) (bin.Encoder, error) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue