Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encrypt profile info in registry #539

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Web app tech refresh

### Changed

- Access tokens on now stored encrypted on device

## [0.35.5] - 2025-01-09

### Added
Expand Down
113 changes: 81 additions & 32 deletions playlet-lib/src/components/Services/ProfilesService/ProfilesService.bs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "pkg:/source/utils/StringUtils.bs"

' TODO:P1 add the ability to change color of profiles
function Init()
m.encryptedSensitiveFields = ["accessToken", "refreshToken"]
m.disableSavingToRegistry = false
content = m.top.findNode("ProfilesContent")
m.top.content = content
Expand All @@ -25,22 +26,26 @@ function LoadProfilesFromRegistry()
' Step 1: Load user profiles from registry
userProfiles = RegistryUtils.Read(RegistryUtils.USER_PROFILES)

if userProfiles = invalid
if StringUtils.IsNullOrEmpty(userProfiles)
userProfiles = {
profiles: []
currentProfile: invalid
}
else
userProfiles = ParseJson(userProfiles)
if not IsAssociativeArray(userProfiles)
userProfiles = {
profiles: []
currentProfile: invalid
}
end if
end if

' Step 2: Migrate existing profiles to new format (if needed)
isDirty = MigrateExistingProfiles(userProfiles)

' Step 3: Save user preferences (if needed)
if isDirty
SaveUserProfilesToRegistry()
end if
' Step 3: Decrypt sensitive fields
DecryptSensitiveFields(userProfiles)

' Step 4: Load user profiles into content nodes
profileNodes = []
Expand Down Expand Up @@ -80,6 +85,11 @@ function LoadProfilesFromRegistry()
end for
end if

' Step 5: Save user preferences (if needed)
if isDirty
SaveUserProfilesToRegistry()
end if

if profileNodes.Count() > 0
AsyncTask.Start(Tasks.ProfilesVerifyTokensTask, {
profilesService: m.top
Expand All @@ -90,40 +100,57 @@ function LoadProfilesFromRegistry()
end function

function MigrateExistingProfiles(userProfiles as object) as boolean
isDirty = false

' v0 -> v1
tokenPayload = RegistryUtils.Read(RegistryUtils.INVIDIOUS_TOKEN)
if tokenPayload = invalid
return false
if tokenPayload <> invalid
authToken = ParseJson(tokenPayload)
RegistryUtils.Delete(RegistryUtils.INVIDIOUS_TOKEN)
if IsAssociativeArray(authToken) and IsString(authToken.instance) and IsString(authToken.token) and IsString(authToken.username)
id = CreateProfileId(authToken.username, authToken.instance)
profileExists = false
for each profile in userProfiles.profiles
if profile.id = id
profileExists = true
exit for
end if
end for
if not profileExists
profile = {
"id": id
"type": "invidious"
"username": authToken.username
"serverUrl": authToken.instance
"accessToken": authToken.token
"color": ColorUtils.RandomSoftColorHex()
}

userProfiles.profiles.Unshift(profile)
if StringUtils.IsNullOrEmpty(userProfiles["currentProfile"])
userProfiles["currentProfile"] = profile.id
end if

isDirty = true
end if
end if
end if

authToken = ParseJson(tokenPayload)
RegistryUtils.Delete(RegistryUtils.INVIDIOUS_TOKEN)

if authToken = invalid or authToken.instance = invalid or authToken.token = invalid or authToken.username = invalid
return false
if ValidInt(userProfiles.__version) = 0
userProfiles.__version = 1
isDirty = true
end if

id = CreateProfileId(authToken.username, authToken.instance)
for each profile in userProfiles.profiles
if profile.id = id
return false
end if
end for
' v1 -> v2
' v2 tokens are encrypted when saved to registry
if userProfiles.__version = 1
userProfiles.__version = 2

profile = {
"id": id
"type": "invidious"
"username": authToken.username
"serverUrl": authToken.instance
"accessToken": authToken.token
"color": ColorUtils.RandomSoftColorHex()
}

userProfiles.profiles.Unshift(profile)
if StringUtils.IsNullOrEmpty(userProfiles["currentProfile"])
userProfiles["currentProfile"] = profile.id
EncryptSensitiveFields(userProfiles)
isDirty = true
end if

return true
return isDirty
end function

function SaveUserProfilesToRegistry() as void
Expand All @@ -132,13 +159,15 @@ function SaveUserProfilesToRegistry() as void
end if

userProfiles = GetProfilesDto(true)
' encrypting sensitive fields before saving to registry starting from v2
EncryptSensitiveFields(userProfiles)

userProfiles.__version = m.top.__version
RegistryUtils.Write(RegistryUtils.USER_PROFILES, FormatJson(userProfiles))
end function

function GetProfilesDto(includeAccessToken as boolean) as object
userProfiles = {
"__version": m.top.__version
"profiles": []
"currentProfile": invalid
}
Expand Down Expand Up @@ -186,6 +215,26 @@ function GetProfilesDto(includeAccessToken as boolean) as object
return userProfiles
end function

function EncryptSensitiveFields(userProfiles as object) as void
for each profile in userProfiles.profiles
for each field in m.encryptedSensitiveFields
if IsString(profile[field])
profile[field] = CryptoUtils.ChannelEncrypt(profile[field])
end if
end for
end for
end function

function DecryptSensitiveFields(userProfiles as object) as void
for each profile in userProfiles.profiles
for each field in m.encryptedSensitiveFields
if IsString(profile[field])
profile[field] = CryptoUtils.ChannelDecrypt(profile[field])
end if
end for
end for
end function

function LoginWithProfile(newProfile as object)
isNewProfile = true
if StringUtils.IsNullOrEmpty(newProfile.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
profiles saved need to be migrated. If the version is less than the
current version, a migration is needed.
-->
<field id="__version" type="integer" value="1" />
<field id="__version" type="integer" value="2" />
<field id="currentProfile" type="node" />
<field id="onProfileLogout" type="boolean" alwaysNotify="true" />
<field id="content" type="node" />
Expand Down
46 changes: 46 additions & 0 deletions playlet-lib/src/source/utils/CryptoUtils.bs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,50 @@ namespace CryptoUtils
digest.Setup(method)
return digest.Process(buffer)
end function

function ChannelEncrypt(input as string) as string
return Encrypt(input, "channel")
end function

function ChannelDecrypt(input as string) as string
return Decrypt(input, "channel")
end function

function Encrypt(input as string, encType as string) as string
if input = ""
return ""
end if

deviceCrypto = CreateObject("roDeviceCrypto")

buffer = CreateObject("roByteArray")
buffer.FromAsciiString(input)

encrypted = deviceCrypto.Encrypt(buffer, encType)
if encrypted = invalid
return ""
end if
return encrypted.ToHexString()
end function

function Decrypt(input as string, encType as string) as string
if input = ""
return ""
end if

deviceCrypto = CreateObject("roDeviceCrypto")

buffer = CreateObject("roByteArray")
buffer.FromHexString(input)

if buffer.Count() = 0
return ""
end if

decrypted = deviceCrypto.Decrypt(buffer, encType)
if decrypted = invalid
return ""
end if
return decrypted.ToAsciiString()
end function
end namespace
Loading