ref: new-login
app/src/main/java/xyz/apiote/bimba/czwek/onboarding/OnboardingActivity.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
// SPDX-FileCopyrightText: Adam Evyčędo // // SPDX-License-Identifier: GPL-3.0-or-later package xyz.apiote.bimba.czwek.onboarding import android.content.Intent import android.content.SharedPreferences import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.edit import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.core.widget.addTextChangedListener import net.openid.appauth.AuthState import net.openid.appauth.AuthorizationException import net.openid.appauth.AuthorizationRequest import net.openid.appauth.AuthorizationResponse import net.openid.appauth.AuthorizationService import net.openid.appauth.AuthorizationServiceConfiguration import net.openid.appauth.AuthorizationServiceConfiguration.RetrieveConfigurationCallback import net.openid.appauth.ResponseTypeValues import xyz.apiote.bimba.czwek.api.Server import xyz.apiote.bimba.czwek.databinding.ActivityOnboardingBinding import xyz.apiote.bimba.czwek.settings.feeds.FeedChooserActivity class OnboardingActivity : AppCompatActivity() { private var _binding: ActivityOnboardingBinding? = null private val binding get() = _binding!! private lateinit var preferences: SharedPreferences private lateinit var authState: AuthState private val activityLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (!FirstRunActivity.getFirstRun(this)) { finish() } } override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) _binding = ActivityOnboardingBinding.inflate(layoutInflater) setContentView(binding.root) ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets -> val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) v.updatePadding(right = insets.right, left = insets.left, bottom = insets.bottom) windowInsets } preferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE) preferences.edit(true) { putBoolean(IN_FEEDS_TRANSACTION, true) } binding.server.editText!!.addTextChangedListener { editable -> binding.accountButton.isEnabled = !editable.isNullOrBlank() binding.anonymousButton.isEnabled = !editable.isNullOrBlank() } if (!FirstRunActivity.getFirstRun(this)) { Server.get(this).let { server -> binding.server.editText!!.setText(server.host) } } binding.accountButton.setOnClickListener { // TODO OIDC flow https://github.com/openid/AppAuth-Android oidcFlow() } binding.anonymousButton.setOnClickListener { moveOn() } } private fun setServer(hostname: String) { preferences.edit(true) { putString(Server.HOST_KEY, hostname) } } private fun moveOn() { // TODO test 401/403 // TODO show rate-limit info, terms and privacy binding.server.editText?.text?.toString()?.let { serverAddress -> setServer(serverAddress) } activityLauncher.launch(Intent(this, FeedChooserActivity::class.java)) } private fun oidcFlow() { AuthorizationServiceConfiguration.fetchFromIssuer( Uri.parse("https://oauth-bimba.apiote.xyz"), RetrieveConfigurationCallback { serviceConfiguration, ex -> if (ex != null) { Log.e("OIDC", "failed to fetch configuration") return@RetrieveConfigurationCallback } authState = AuthState(serviceConfiguration!!) /* {"client_id":"46e1be27-a747-4507-a1d4-c383f137745f","client_name":"Bimba","client_secret_expires_at":0,"client_uri":"https://bimba.app","contacts":["questions@bimba.app"],"created_at":"2024-09-09T12:41:30Z","grant_types":["authorization_code","refresh_token"],"jwks":{},"logo_uri":"https://bimba.app/bimba.svg","metadata":{},"owner":"","policy_uri":"","redirect_uris":["bimba://callback"],"registration_access_token":"ory_at_xQWn3kMRuev5ZmCM_BgwnuOo57GRKWz-7LAA8EH2OTA.GXZCCf7XCg8fIwjpGZjZGdgJPMSsMSM9E-6B7bE8nB0","registration_client_uri":"https://oauth-bimba.apiote.xyz/oauth2/register/","request_object_signing_alg":"RS256","response_types":["code","id_token"],"scope":"openid offline","skip_consent":false,"skip_logout_consent":false,"subject_type":"public","token_endpoint_auth_method":"none","tos_uri":"","updated_at":"2024-09-09T14:41:29.982386+02:00","userinfo_signed_response_alg":"none"} */ val authRequest = AuthorizationRequest.Builder( serviceConfiguration, "46e1be27-a747-4507-a1d4-c383f137745f", ResponseTypeValues.CODE, Uri.parse("bimba://callback") ) .setScope("openid offline") .build(); val authIntent = AuthorizationService(this).getAuthorizationRequestIntent(authRequest) startActivityForResult(authIntent, RC_AUTH) }) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == RC_AUTH) { val resp = AuthorizationResponse.fromIntent(data!!) val ex = AuthorizationException.fromIntent(data) if (resp != null) { authState.update(resp, ex) AuthorizationService(this).performTokenRequest( resp.createTokenExchangeRequest() ) { tokenResponse, tokenException -> if (tokenResponse != null) { authState.update(resp, ex) getSharedPreferences("auth", MODE_PRIVATE).edit { putString("state", authState.jsonSerializeString()) } if (tokenResponse.idToken == null) { // TODO openid is needed; show some dialogue } Log.i("OIDC", "got token: id: ${tokenResponse.idToken}\n access: ${tokenResponse.accessToken}\n params: ${tokenResponse.additionalParameters}") // TODO moveOn() } else { // FIXME token exchange failed: Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). Log.e("OIDC", "token exchange failed: ${tokenException!!.message}") tokenException.printStackTrace() } } } else { Log.e("OIDC", "auth failed: ${ex!!.message}") } } } companion object { const val PREFERENCES_NAME = "shp" const val IN_FEEDS_TRANSACTION = "inFeedsTransaction" const val RC_AUTH = 42 } } |