{"record":{"id":"cd4a8c2a38e3b999","repo":"immich-app/immich","slug":"password-required","errorCode":null,"errorMessage":"Password required","messagePattern":"Password required","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"server/src/services/shared-link.service.ts","lineNumber":58,"sourceCode":"      throw new UnauthorizedException('Invalid password');\n    }\n\n    return {\n      sharedLink: mapSharedLink(sharedLink, { stripAssetMetadata: !sharedLink.showExif }),\n      token: this.asToken({ id, password }),\n    };\n  }\n\n  async getMine(auth: AuthDto, authTokens: string[]) {\n    if (!auth.sharedLink) {\n      throw new ForbiddenException();\n    }\n\n    const sharedLink = await this.findOrFail(auth.user.id, auth.sharedLink.id);\n    const { id, password } = sharedLink;\n\n    if (password && !authTokens.includes(this.asToken({ id, password }))) {\n      throw new UnauthorizedException('Password required');\n    }\n\n    return mapSharedLink(sharedLink, { stripAssetMetadata: !sharedLink.showExif });\n  }\n\n  async get(auth: AuthDto, id: string): Promise<SharedLinkResponseDto> {\n    const sharedLink = await this.findOrFail(auth.user.id, id);\n    return mapSharedLink(sharedLink, { stripAssetMetadata: false });\n  }\n\n  async create(auth: AuthDto, dto: SharedLinkCreateDto): Promise<SharedLinkResponseDto> {\n    switch (dto.type) {\n      case SharedLinkType.Album: {\n        if (!dto.albumId) {\n          throw new BadRequestException('Invalid albumId');\n        }\n        await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [dto.albumId] });\n        break;","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/shared-link.service.ts#L40-L76","documentation":"SharedLink.getMine returns the caller's own shared link but enforces that the request's auth token matches the token derived from the link's password. If the link has a password and the presented auth token is not the expected password token, access is refused with UnauthorizedException('Password required').","triggerScenarios":"GET /shared-links/me (getMine) authenticated with auth.sharedLink credentials whose token is not in authTokens for a password-protected link — e.g. accessing the link metadata without first completing the password login.","commonSituations":"Client fetches /me directly with the share key without exchanging the password for a token; owner changed the link password so the cached token no longer matches.","solutions":["Complete the shared-link password login first, then use the returned token for getMine.","If the link password changed, re-authenticate with the new password to mint a fresh token.","For passwordless links no token exchange is needed — verify the right link/key is used."],"exampleFix":"// before\nconst link = await api.getMySharedLink(shareKeyToken); // token predates password\n// after\nconst { token } = await api.sharedLinkLogin(shareKey, { password });\nconst link = await api.getMySharedLink(token);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await api.getMySharedLink(token);\n} catch (e) {\n  if (e.status === 401 && e.message === 'Password required') {\n    const { token: fresh } = await api.sharedLinkLogin(shareKey, { password });\n    return await api.getMySharedLink(fresh);\n  }\n  throw e;\n}","preventionTips":["Always run the password login exchange before getMine for protected links.","Discard cached tokens after the link password changes.","Store the token returned by login, not the raw share key."],"tags":["shared-link","password","authentication","unauthorized"],"backgroundTag":"authentication-required","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}