{"record":{"id":"8c5d9378293c1845","repo":"livekit/livekit","slug":"expired","errorCode":null,"errorMessage":"expired","messagePattern":"expired","errorType":"error_code","errorClass":"ErrExpired","httpStatus":null,"severity":"error","filePath":"pkg/service/turn.go","lineNumber":47,"sourceCode":"\t\"github.com/pkg/errors\"\n\n\t\"github.com/livekit/protocol/auth\"\n\t\"github.com/livekit/protocol/livekit\"\n\t\"github.com/livekit/protocol/logger\"\n\t\"github.com/livekit/protocol/logger/pionlogger\"\n\n\t\"github.com/livekit/livekit-server/pkg/config\"\n\t\"github.com/livekit/livekit-server/pkg/telemetry\"\n\t\"github.com/livekit/livekit-server/pkg/telemetry/prometheus\"\n)\n\nconst (\n\tLivekitRealm = \"livekit\"\n\n\tallocateRetries = 50\n)\n\nvar ErrExpired = errors.New(\"expired\")\n\n// parsePeerCIDRs compiles a list of CIDR strings, failing with a field-specific\n// error on any invalid entry so a malformed peer policy is never silently ignored.\nfunc parsePeerCIDRs(field string, cidrs []string) ([]*net.IPNet, error) {\n\tparsed := make([]*net.IPNet, 0, len(cidrs))\n\tfor _, cidr := range cidrs {\n\t\t_, ipnet, err := net.ParseCIDR(cidr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid CIDR %q in %s: %w\", cidr, field, err)\n\t\t}\n\t\tparsed = append(parsed, ipnet)\n\t}\n\treturn parsed, nil\n}\n\nfunc NewTurnServer(conf *config.Config, authHandler turn.AuthHandler, standalone bool) (*turn.Server, error) {\n\tturnConf := conf.TURN\n\tif !turnConf.Enabled {","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/livekit/livekit/blob/ee45c3f0b1a83bf4352dbccb6607ebf70b2a5de6/pkg/service/turn.go#L29-L65","documentation":"ErrExpired is the sentinel returned by TURN credential helpers when expiry is zero or already in the past. ParseUsername rejects credentials encoding a zero expiry, and CreatePassword refuses to mint a password for a non-future expiry, since TURN short-lived credentials must carry a valid future timestamp.","triggerScenarios":"Calling TURNAuthHandler.CreatePassword with expiry == 0 or time.Now() after time.Unix(expiry,0); ParseUsername decoding a username whose embedded expiry is 0; callers computing expiry from an unset AccessToken/notBefore-less token.","commonSituations":"Generating TURN credentials with a JWT/room token whose expiry field is absent; integer/timezone math producing 0; passing Unix seconds where the caller thought it was a duration.","solutions":["Pass a future Unix timestamp: time.Now().Add(ttl).Unix() as expiry","Check the source token/access credentials actually contain a valid exp claim","Treat errors.Is(err, turn.ErrExpired) as 'issue a fresh credential' and regenerate"],"exampleFix":"// before\npwd, err := handler.CreatePassword(apiKey, pID, 0)\n// after\nexpiry := time.Now().Add(time.Hour).Unix()\npwd, err := handler.CreatePassword(apiKey, pID, expiry)","handlingStrategy":"try-catch","validationCode":"func validExpiry(expiry int64) bool {\n    return expiry != 0 && time.Now().Before(time.Unix(expiry, 0))\n}\nif !validExpiry(expiry) {\n    return errors.New(\"refusing to create TURN credentials: expiry not in future\")\n}","typeGuard":"func isErrExpired(err error) bool { return errors.Is(err, turn.ErrExpired) }","tryCatchPattern":"if _, err := handler.CreatePassword(apiKey, pID, expiry); err != nil {\n    if errors.Is(err, turn.ErrExpired) {\n        // mint a fresh credential with a future expiry\n    }\n}","preventionTips":["Always compute expiry as time.Now().Add(ttl).Unix()","Verify source tokens carry a valid exp claim before deriving TURN credentials","Centralize credential creation in one helper that validates expiry"],"tags":["turn","authentication","expired-credentials"],"backgroundTag":"turn-credential-expired","analyzedSha":"ee45c3f0b1a83bf4352dbccb6607ebf70b2a5de6","analyzedAt":"2026-09-02T03:56:08.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}