Skip to content

Commit 87abb40

Browse files
committed
Identity Updates
1 parent 3f03f35 commit 87abb40

File tree

8 files changed

+193
-4
lines changed

8 files changed

+193
-4
lines changed

component-stargate/src/commonMain/kotlin/com/mooncloak/vpn/component/stargate/entanglement/Contact.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ import kotlinx.serialization.Serializable
99
*/
1010
@Serializable
1111
public data class Contact public constructor(
12-
@SerialName(value = "name") public val name: String
12+
@SerialName(value = "name") public val name: String,
13+
@SerialName(value = "images") public val images: ContactImages? = null
1314
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.mooncloak.vpn.component.stargate.entanglement
2+
3+
import com.mooncloak.vpn.component.stargate.message.model.ImageContent
4+
import kotlinx.serialization.SerialName
5+
import kotlinx.serialization.Serializable
6+
7+
@Serializable
8+
public data class ContactImages public constructor(
9+
@SerialName(value = "avatar") public val avatar: ImageContent? = null
10+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.mooncloak.vpn.component.stargate.entanglement
2+
3+
import com.mooncloak.vpn.util.shared.validation.fromOrNull
4+
5+
public interface DIDDocumentIdentityHandleProvider {
6+
7+
public fun get(document: DIDDocument): List<IdentityHandle>
8+
9+
public companion object
10+
}
11+
12+
internal object AtProtocolDidDocumentIdentityHandleProvider : DIDDocumentIdentityHandleProvider {
13+
14+
override fun get(document: DIDDocument): List<IdentityHandle> =
15+
document.alsoKnownAs.filter { it.startsWith("at://") }
16+
.map { it.removePrefix("at://") }
17+
.mapNotNull { IdentityHandle.fromOrNull(it) }
18+
}

component-stargate/src/commonMain/kotlin/com/mooncloak/vpn/component/stargate/entanglement/Identity.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.mooncloak.vpn.component.stargate.entanglement
22

3+
import kotlinx.datetime.Clock
34
import kotlinx.datetime.Instant
45
import kotlinx.serialization.SerialName
56
import kotlinx.serialization.Serializable
7+
import kotlin.time.Duration
8+
import kotlin.time.Duration.Companion.minutes
69

710
@Serializable
811
public data class Identity public constructor(
@@ -13,3 +16,14 @@ public data class Identity public constructor(
1316
@SerialName(value = "contact") public val contact: Contact? = null,
1417
@SerialName(value = "resolved") public val resolved: Instant
1518
)
19+
20+
public fun Identity.test() {
21+
if (handle != null) {
22+
document.alsoKnownAs.filter { it.startsWith("at://") }
23+
}
24+
}
25+
26+
public fun Identity.isValid(
27+
at: Instant = Clock.System.now(),
28+
period: Duration = 15.minutes
29+
): Boolean = (resolved + period) > at
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.mooncloak.vpn.component.stargate.entanglement
2+
3+
import com.mooncloak.vpn.data.shared.repository.MutableRepository
4+
import kotlin.coroutines.cancellation.CancellationException
5+
6+
public interface IdentityRepository : MutableRepository<Identity> {
7+
8+
@Throws(IllegalArgumentException::class, CancellationException::class)
9+
public suspend fun getByDid(did: DID): Identity =
10+
get(id = did.value)
11+
12+
@Throws(IllegalArgumentException::class, CancellationException::class)
13+
public suspend fun getByHandle(handle: IdentityHandle): Identity
14+
15+
public companion object
16+
}
17+
18+
public suspend fun IdentityRepository.getByDidOrNull(did: DID): Identity? =
19+
try {
20+
getByDid(did = did)
21+
} catch (_: NoSuchElementException) {
22+
null
23+
}
24+
25+
public suspend fun IdentityRepository.getByHandleOrNull(handle: IdentityHandle): Identity? =
26+
try {
27+
getByHandle(handle = handle)
28+
} catch (_: NoSuchElementException) {
29+
null
30+
}

component-stargate/src/commonMain/kotlin/com/mooncloak/vpn/component/stargate/entanglement/IdentityResolver.kt

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
package com.mooncloak.vpn.component.stargate.entanglement
22

33
import com.mooncloak.vpn.util.shared.validation.fromOrNull
4+
import kotlinx.datetime.Clock
45
import kotlin.coroutines.cancellation.CancellationException
56

67
public interface IdentityResolver {
78

8-
@Throws(NoSuchElementException::class, IllegalArgumentException::class, CancellationException::class)
9+
@Throws(
10+
NoSuchElementException::class,
11+
IllegalArgumentException::class,
12+
IllegalStateException::class,
13+
CancellationException::class
14+
)
915
public suspend fun resolve(did: DID): Identity
1016

11-
@Throws(NoSuchElementException::class, IllegalArgumentException::class, CancellationException::class)
17+
@Throws(
18+
NoSuchElementException::class,
19+
IllegalArgumentException::class,
20+
IllegalStateException::class,
21+
CancellationException::class
22+
)
1223
public suspend fun resolve(handle: IdentityHandle): Identity
1324

14-
@Throws(NoSuchElementException::class, IllegalArgumentException::class, CancellationException::class)
25+
@Throws(
26+
NoSuchElementException::class,
27+
IllegalArgumentException::class,
28+
IllegalStateException::class,
29+
CancellationException::class
30+
)
1531
public suspend fun resolve(value: String): Identity {
1632
val did = DID.fromOrNull(value)
1733

@@ -38,6 +54,8 @@ public suspend fun IdentityResolver.resolveOrNull(did: DID): Identity? =
3854
null
3955
} catch (_: IllegalArgumentException) {
4056
null
57+
} catch (_: IllegalStateException) {
58+
null
4159
}
4260

4361
public suspend fun IdentityResolver.resolveOrNull(handle: IdentityHandle): Identity? =
@@ -47,6 +65,8 @@ public suspend fun IdentityResolver.resolveOrNull(handle: IdentityHandle): Ident
4765
null
4866
} catch (_: IllegalArgumentException) {
4967
null
68+
} catch (_: IllegalStateException) {
69+
null
5070
}
5171

5272
public suspend fun IdentityResolver.resolveOrNull(value: String): Identity? =
@@ -56,4 +76,91 @@ public suspend fun IdentityResolver.resolveOrNull(value: String): Identity? =
5676
null
5777
} catch (_: IllegalArgumentException) {
5878
null
79+
} catch (_: IllegalStateException) {
80+
null
5981
}
82+
83+
internal class DefaultIdentityResolver internal constructor(
84+
private val identityRepository: IdentityRepository,
85+
private val didResolver: DIDResolver,
86+
private val didDocumentResolver: DIDDocumentResolver,
87+
private val didDocumentIdentityHandleProvider: DIDDocumentIdentityHandleProvider,
88+
private val profileResolver: ProfileResolver,
89+
private val clock: Clock
90+
) : IdentityResolver {
91+
92+
override suspend fun resolve(did: DID): Identity {
93+
val identity = identityRepository.getByDidOrNull(did = did)
94+
95+
if (identity != null && identity.isValid(at = clock.now())) {
96+
return identity
97+
}
98+
99+
val document = didDocumentResolver.resolve(did = did)
100+
101+
return getIdentity(
102+
handle = identity?.handle,
103+
contact = identity?.contact,
104+
did = did,
105+
document = document
106+
)
107+
}
108+
109+
override suspend fun resolve(handle: IdentityHandle): Identity {
110+
val did = didResolver.resolve(handle = handle)
111+
112+
val identity = identityRepository.getByDidOrNull(did = did)
113+
114+
if (identity != null && identity.handle != handle) {
115+
throw IllegalStateException("Provided Identity Handle value '$handle' did not match the handle in the stored value '${identity.handle}'.")
116+
}
117+
118+
if (identity != null && identity.isValid(at = clock.now())) {
119+
return identity
120+
}
121+
122+
val document = didDocumentResolver.resolve(did = did)
123+
124+
return getIdentity(
125+
handle = handle,
126+
contact = identity?.contact,
127+
did = did,
128+
document = document
129+
)
130+
}
131+
132+
private suspend fun getIdentity(
133+
handle: IdentityHandle?,
134+
contact: Contact?,
135+
did: DID,
136+
document: DIDDocument
137+
): Identity {
138+
if (handle != null) {
139+
val allHandles = didDocumentIdentityHandleProvider.get(document)
140+
141+
if (!allHandles.contains(handle)) {
142+
throw IllegalStateException("Identity Handle used to obtain DID, is not present in the associated DID Document. This means the Identity Handle does not currently belong to the DID.")
143+
}
144+
}
145+
146+
val profile = profileResolver.resolve(document)
147+
val now = clock.now()
148+
149+
val identity = Identity(
150+
did = did,
151+
document = document,
152+
handle = handle,
153+
profile = profile,
154+
contact = contact,
155+
resolved = now
156+
)
157+
158+
identityRepository.upsert(
159+
id = identity.did.value,
160+
insert = { identity },
161+
update = { identity }
162+
)
163+
164+
return identity
165+
}
166+
}

component-stargate/src/commonMain/kotlin/com/mooncloak/vpn/component/stargate/entanglement/Profile.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable
77

88
@Serializable
99
public data class Profile public constructor(
10+
@SerialName(value = "uri") public val uri: String? = null,
1011
@SerialName(value = "display_name") public val displayName: String? = null,
1112
@SerialName(value = "description") @Serializable(with = AnnotatedStringSerializer::class) public val description: AnnotatedString? = null,
1213
@SerialName(value = "images") public val images: ProfileImages? = null,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.mooncloak.vpn.component.stargate.entanglement
2+
3+
public interface ProfileResolver {
4+
5+
public suspend fun resolve(document: DIDDocument): Profile?
6+
7+
public companion object
8+
}

0 commit comments

Comments
 (0)