Skip to content

Commit

Permalink
Merge pull request #23 from opatry/google-tasks
Browse files Browse the repository at this point in the history
Google tasks
  • Loading branch information
opatry authored Sep 30, 2024
2 parents d23a03f + 29e0d3b commit 308a4ea
Show file tree
Hide file tree
Showing 23 changed files with 1,579 additions and 0 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Build & Test

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
build:

runs-on: ubuntu-latest
permissions:
contents: read
checks: write
id-token: write

steps:
- uses: actions/checkout@v4

- name: ☕ Setup JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'adopt'

- name: 🐘 Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
add-job-summary-as-pr-comment: on-failure

- name: 🔨 Build
run: ./gradlew --no-daemon assemble
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![Build & Test](https://github.com/opatry/tasks-app/actions/workflows/build.yml/badge.svg)](https://github.com/opatry/tasks-app/actions/workflows/build.yml)

# Taskfolio

A basic TODO list application based on [Google Tasks REST API](https://developers.google.com/tasks/reference/rest) to showcase [KMP](https://kotlinlang.org/docs/multiplatform.html) app capabilities.
Expand All @@ -7,6 +9,7 @@ A basic TODO list application based on [Google Tasks REST API](https://developer
- [Kotlin](https://kotlinlang.org/), [Multiplatform (KMP)](https://kotlinlang.org/docs/multiplatform.html) (currently Desktop & Android are supported)
- [Kotlin coroutines](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html)
- [Kotlin multiplatform](https://kotlinlang.org/docs/multiplatform.html) (aka KMP)
- [Ktor client](https://ktor.io/) (+ [Kotlinx serialization](https://kotlinlang.org/docs/serialization.html))

## License

Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@

plugins {
alias(libs.plugins.jetbrains.kotlin.multiplatform) apply false
alias(libs.plugins.jetbrains.kotlin.serialization) apply false
}
37 changes: 37 additions & 0 deletions google/oauth/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 Olivier Patry
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

plugins {
alias(libs.plugins.jetbrains.kotlin.multiplatform)
alias(libs.plugins.jetbrains.kotlin.serialization)
}

kotlin {
jvm()

sourceSets {
commonMain.dependencies {
implementation(libs.bundles.ktor.client)
implementation(libs.bundles.ktor.server)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2024 Olivier Patry
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package net.opatry.google.auth

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
/**
* Represents a Google OAuth2 client credentials (`client_secret_xxx.apps.googleusercontent.com.json` files).
*/
data class GoogleAuth(
@SerialName("web")
val webCredentials: Credentials.Web? = null,
@SerialName("installed")
val installedCredentials: Credentials.Installed? = null,
) {
sealed class Credentials {
abstract val clientId: String
abstract val projectId: String
abstract val authUri: String
abstract val tokenUri: String
abstract val authProviderX509CertUrl: String

@Serializable
data class Web(
@SerialName("client_id")
override val clientId: String,
@SerialName("project_id")
override val projectId: String,
@SerialName("auth_uri")
override val authUri: String,
@SerialName("token_uri")
override val tokenUri: String,
@SerialName("auth_provider_x509_cert_url")
override val authProviderX509CertUrl: String,
@SerialName("client_secret")
val clientSecret: String,
@SerialName("redirect_uris")
val redirectUris: List<String>,
) : Credentials()

@Serializable
data class Installed(
@SerialName("client_id")
override val clientId: String,
@SerialName("project_id")
override val projectId: String,
@SerialName("auth_uri")
override val authUri: String,
@SerialName("token_uri")
override val tokenUri: String,
@SerialName("auth_provider_x509_cert_url")
override val authProviderX509CertUrl: String,
) : Credentials()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2024 Olivier Patry
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package net.opatry.google.auth

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.opatry.google.auth.GoogleAuthenticator.OAuthToken.TokenType.Bearer

interface GoogleAuthenticator {
@JvmInline
value class Scope(val value: String) {
companion object {
val Profile = Scope("https://www.googleapis.com/auth/userinfo.profile")
val Email = Scope("https://www.googleapis.com/auth/userinfo.email")
val OpenID = Scope("openid")
}
}

@Serializable
/**
* @property accessToken The token that your application sends to authorize a Google API request.
* @property expiresIn The remaining lifetime of the access token in seconds.
* @property idToken **Note:** This property is only returned if your request included an identity scope, such as `openid`, `profile`, or `email`. The value is a JSON Web Token (JWT) that contains digitally signed identity information about the user.
* @property refreshToken A token that you can use to obtain a new access token. Refresh tokens are valid until the user revokes access. Note that refresh tokens are always returned for installed applications.
* @property scope The scopes of access granted by the [accessToken] expressed as a list of [Scope].
* @property tokenType The type of token returned. At this time, this field's value is always set to `Bearer`.
*/
data class OAuthToken(
@SerialName("access_token")
val accessToken: String,

@SerialName("expires_in")
val expiresIn: Long,

@SerialName("id_token")
val idToken: String? = null,

@SerialName("refresh_token")
val refreshToken: String? = null,

@SerialName("scope")
val scope: String,

@SerialName("token_type")
val tokenType: TokenType,
) {
/**
* Value is case insensitive.
*
* @property Bearer `"Bearer"` token type defined in [RFC6750](https://datatracker.ietf.org/doc/html/rfc6750) is utilized by simply including the access token string in the request.
*/
enum class TokenType {
@SerialName("Bearer")
Bearer,
}
}

sealed class Grant {
abstract val type: String

data class AuthorizationCode(val code: String) : Grant() {
override val type: String = "authorization_code"
}

data class RefreshToken(val refreshToken: String) : Grant() {
override val type: String
get() = "refresh_token"
}
}

/**
* @param scopes Scopes to request to the user.
* @param force To force user consent screen again (allowing to get a refresh token).
* @param requestUserAuthorization The data needed to request user authorization before redirection
*
* @return auth code
*
* @see Scope
*/
suspend fun authorize(scopes: List<Scope>, force: Boolean = false, requestUserAuthorization: (data: Any) -> Unit): String

/**
* @param code The code obtained through [authorize].
*
* @return OAuth access token
*
* @see Scope
*/
suspend fun getToken(grant: Grant): OAuthToken
}
Loading

0 comments on commit 308a4ea

Please sign in to comment.