diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 6cfa6ac..c3bd4d6 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -1,16 +1,26 @@ -import { defineConfig } from 'astro/config'; -import starlight from '@astrojs/starlight'; -import rehypeMermaid from 'rehype-mermaid'; -import tailwind from '@astrojs/tailwind'; -import starlightOpenAPI, { openAPISidebarGroups } from 'starlight-openapi'; +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; +import rehypeMermaid from "rehype-mermaid"; +import tailwind from "@astrojs/tailwind"; +import starlightOpenAPI, { openAPISidebarGroups } from "starlight-openapi"; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; // import starlightTypeDoc, { typeDocSidebarGroup } from "starlight-typedoc"; -import react from '@astrojs/react'; - -import mdx from '@astrojs/mdx'; -import basicSsl from '@vitejs/plugin-basic-ssl'; +import react from "@astrojs/react"; +import mdx from "@astrojs/mdx"; +import basicSsl from '@vitejs/plugin-basic-ssl' +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); // https://astro.build/config export default defineConfig({ vite: { + resolve: { + alias: { + '@/lib': path.resolve(__dirname, './src/lib'), + '@/assets': path.resolve(__dirname, './src/assets'), + '@/components': path.resolve(__dirname, './src/components'), + }, + }, plugins: [basicSsl()], server: { https: true, @@ -34,9 +44,58 @@ export default defineConfig({ head: [ // GTM Script { - tag: 'script', - attrs: { - 'is:inline': true, + label: "Integrations", + link: "/server/integrations", + }, + ...openAPISidebarGroups, + ] + }, { + label: "Clients", + collapsed: true, + items: [{ + label: "Android", + collapsed: true, + items: [ + { + label: "Introduction", + link: "/clients/android/introduction", + }, + { + label: "Registration", + link: "/clients/android/registration", + }, + { + label: "Authentication", + link: "/clients/android/authentication", + }, + { + label: "Peer Offer", + link: "/clients/android/offer", + }, + { + label: "Peer Answer", + link: "/clients/android/answer", + }, + { + label: "Provider Service", + badge: { + text: "^14", + variant: "danger" + }, + items: [ + { + label: "Introduction", + link: "/clients/android/provider-service/introduction", + }, + { + label: "Registration", + link: "/clients/android/provider-service/registration", + }, + { + label: "Authentication", + link: "/clients/android/provider-service/authentication", + } + ] }, content: ` (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': diff --git a/docs/src/components/CodeUri.astro b/docs/src/components/CodeUri.astro new file mode 100644 index 0000000..81fc2bd --- /dev/null +++ b/docs/src/components/CodeUri.astro @@ -0,0 +1,15 @@ +--- +import { Code } from '@astrojs/starlight/components'; +import { fetchAndExtract } from '@/lib/fetchAndExtract.js'; + +const { uri, lines, lang, title, mark, frame } = Astro.props; + +// 'lines' can optionally be specified in the following formats: +// - '1-3' (inclusive range) +// - '5' (single line) +// - '1-10,15-20' (multiple ranges) +// - '1-3,5,7-9' (mixed) +const selectedCode = await fetchAndExtract(uri, lines); +--- + + diff --git a/docs/src/content/docs/clients/android/provider-service/authenticate.md b/docs/src/content/docs/clients/android/provider-service/authenticate.md deleted file mode 100644 index a9f70d7..0000000 --- a/docs/src/content/docs/clients/android/provider-service/authenticate.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "Android 14: Authenticate" ---- - -```kotlin -// MainActivity.kt -val request = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent) -if (request.callingRequest is CreatePublicKeyCredentialRequest) { - val result = viewModel.processCreatePasskey(this@CreatePasskeyActivity, request) -} else { - val text = resources.getString(R.string.get_passkey_error) -} -``` diff --git a/docs/src/content/docs/clients/android/provider-service/authentication.mdx b/docs/src/content/docs/clients/android/provider-service/authentication.mdx new file mode 100644 index 0000000..a5a5a02 --- /dev/null +++ b/docs/src/content/docs/clients/android/provider-service/authentication.mdx @@ -0,0 +1,117 @@ +--- +title: "Android 14: Authentication Service" +--- + +import {Steps, TabItem, Tabs} from '@astrojs/starlight/components'; +import CodeUri from "@/components/CodeUri.astro"; + +To handle sign in requests in your credential provider service, complete the steps shown in the following sections. + +## Query + +User sign-in is handled with the following steps: + + +1. When a device tries to sign in a user, it prepares a [GetCredentialRequest](https://developer.android.com/reference/androidx/credentials/GetCredentialRequest) instance. +2. The Android framework propagates this request to all applicable credential providers by binding to these services. +3. The provider service then receives a `BeginGetCredentialRequest` that contains a list of `BeginGetCredentialOption`, + each of which contains parameters that can be used to retrieve matching credentials. + + +To handle this request in your credential provider service, +you can override the `onBeginGetCredentialRequest()` method as shown in the following example: + +```kotlin +package foundation.algorand.demo + +class CustomProviderService: CredentialProviderService() { + companion object { + const val GET_PASSKEY_INTENT = 1 + const val GET_PASSKEY_ACTION = "foundation.algorand.demo.GET_PASSKEY" + } + + /** + * Handle Get Credential Requests + */ + @RequiresApi(Build.VERSION_CODES.S) + override fun onBeginGetCredentialRequest( + request: BeginGetCredentialRequest, + cancellationSignal: CancellationSignal, + callback: OutcomeReceiver, + ) { + try { + callback.onResult(processGetCredentialRequest(request)) + } catch (e: GetCredentialException) { + callback.onError(GetCredentialUnknownException()) + } + } + + /** + * Get a list of available PublicKeyCredential Entries + */ + private fun processGetCredentialRequest(request: BeginGetCredentialRequest): BeginGetCredentialResponse{ + Log.v(TAG, "processing GetCredentialRequest") + val deferredCredentials: Deferred> = scope.async { + credentialRepository.getDatabase(this@LiquidCredentialProviderService).credentialDao().getAllRegular() + } + val credentials = runBlocking { + deferredCredentials.await() + } + return BeginGetCredentialResponse(credentials.map { + val data = Bundle() + data.putString("credentialId", it.credentialId) + data.putString("userHandle", it.userHandle) + PublicKeyCredentialEntry.Builder( + this@LiquidCredentialProviderService, + it.userHandle, + createNewPendingIntent(GET_PASSKEY_ACTION, GET_PASSKEY_INTENT, data), + request.beginGetCredentialOptions[0] as BeginGetPublicKeyCredentialOption + ) + .setIcon(Icon.createWithResource(this@LiquidCredentialProviderService, R.mipmap.ic_launcher)) + .build() + }) + } + + /** + * This method creates a new PendingIntent for the given action and request code. + */ + private fun createNewPendingIntent(action: String, requestCode: Int, extra: Bundle?): PendingIntent { + val intent = Intent(action).setPackage(PACKAGE_NAME) + + if (extra != null) { + intent.putExtra("CREDENTIAL_DATA", extra) + } + return PendingIntent.getActivity( + applicationContext, requestCode, + intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) + ) + } +} +``` + +## Selection + +Once you query and populate the credentials, +now you need to handle the selection phase for the credentials being selected by the user. + + +1. In the `onCreate` method of the corresponding Activity, retrieve the associated intent, and pass to [PendingIntentHandler.retrieveProviderGetCredentialRequest()](https://developer.android.com/reference/androidx/credentials/provider/ProviderGetCredentialRequest). +2. Extract the [GetPublicKeyCredentialOption](https://developer.android.com/reference/androidx/credentials/GetPublicKeyCredentialOption) from the request retrieved above. Subsequently, extract the `requestJson` and `clientDataHash` from this option. +3. Extract the `credentialId` from the intent extra, which was populated by the credential provider when the corresponding `PendingIntent` was set up. +4. Extract the passkey from your local database using the request parameters accessed above. +5. Assert that the passkey is valid with extracted metadata, and user verification. +6. Construct a JSON response based on the W3 Web Authentication Assertion spec. +7. Construct a PublicKeyCredential using the JSON generated above and set it on a final GetCredentialResponse. + + +The following example illustrates how these steps can be implemented: + + + + + + + + + + diff --git a/docs/src/content/docs/clients/android/provider-service/introduction.mdx b/docs/src/content/docs/clients/android/provider-service/introduction.mdx index a4751dc..9d96105 100644 --- a/docs/src/content/docs/clients/android/provider-service/introduction.mdx +++ b/docs/src/content/docs/clients/android/provider-service/introduction.mdx @@ -1,8 +1,8 @@ --- -title: "Android: Provider Service" +title: "Android 14: Provider Service" --- -import {Aside} from '@astrojs/starlight/components'; +import { Aside, Code, Steps } from "@astrojs/starlight/components";