All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.stormbit.vksdk.vkapi.Auth.kt Maven / Gradle / Ivy

The newest version!
package com.github.stormbit.vksdk.vkapi

import com.github.stormbit.vksdk.clients.VkUserClient
import com.github.stormbit.vksdk.exceptions.AuthException
import com.github.stormbit.vksdk.exceptions.BanException
import com.github.stormbit.vksdk.exceptions.TwoFactorException
import com.github.stormbit.vksdk.objects.TwoFactor
import com.github.stormbit.vksdk.utils.*
import com.github.stormbit.vksdk.utils.append
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.features.cookies.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import org.slf4j.LoggerFactory
import com.github.stormbit.vksdk.objects.Captcha as CaptchaObject

private val RE_LOGIN_HASH = Regex("name=\"lg_h\" value=\"([a-z0-9]+)\"")
private val RE_CAPTCHA_ID = Regex("onLoginCaptcha\\('(\\d+)'")
private val RE_AUTH_HASH = Regex("\\{.*?act: 'a_authcheck_code'.+?hash: '([a-z_0-9]+)'.*\\}")
private val RE_TOKEN_URL = Regex("location\\.href = \"(.*?)\"\\+addr;")
private val DEFAULT_USER_SCOPES = VkUserPermissions().apply {
    all = true
}

class Auth(
    private val login: String,
    private val password: String,
    private val httpClient: HttpClient,
    private var appId: Int = 6222115,
    private val scope: VkUserPermissions = DEFAULT_USER_SCOPES,
    private val redirectUrl: String = ""
) {
    companion object {
        private const val STRING_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
    }

    private val log = LoggerFactory.getLogger(Auth::class.java)

    suspend fun authByVk(
        captcha: CaptchaObject? = null,
        twoFactor: TwoFactor? = null
    ): Response {
        val parameters = Parameters.build {
            append("captcha_sid", captcha?.captchaSid)
            append("captcha_key", captcha?.captchaCode)
            append("code", twoFactor?.code)
            append("remember", twoFactor?.remember?.toInt())
        }

        return oauth(parameters)
    }

    private suspend fun oauth(
        parameters: Parameters = parametersOf()
    ): Response {
        log.info("Logging in...")

        val prms = Parameters.build {
            append("grant_type", "password")
            append("client_id", VkUserClient.CLIENT_ID)
            append("client_secret", VkUserClient.CLIENT_SECRET)
            append("username", login)
            append("password", password)
            append("v", VK_API_VERSION)
            append("2fa_supported", 1)

            appendAll(parameters)
        }

        val response = httpClient.get(VkUserClient.BASE_PROXY_OAUTH_URL + "token") {
            url.parameters.appendAll(prms)
            headers.appendAll(VkUserClient.HEADER)
        }

        val responseParsed = json.parseToJsonElement(response).jsonObject

        if ("error" in responseParsed.toString()) {
            if ("need_captcha" == responseParsed.getString("error")) {
                log.info("Captcha code is required")

                val captchaSid = responseParsed.getString("captcha_sid")!!
                val captchaUrl = "https://api.vk.com/captcha.php?sid=$captchaSid"

                return Response.Captcha(captchaSid, captchaUrl)
            } else if ("need_validation" == responseParsed.getString("error")) {
                if ("2fa_sms" in responseParsed.toString()) {
                    return Response.TwoFactor("")
                } else if ("ban_info" in responseParsed.toString()) {
                    throw BanException(responseParsed.getString("error_description"))
                }
            } else if ("invalid_request" == responseParsed.getString("error")) {
                if ("wrong_otp" in responseParsed.toString()) {
                    return Response.TwoFactor("")
                }
            } else if ("invalid_client" == responseParsed.getString("error")) {
                return Response.Error(ErrorType.INVALID_CLIENT)
            } else {
                throw AuthException("Error: ${responseParsed["error_description"]}")
            }
        }

        log.info("Authentication succeeded!")

        val accessToken = responseParsed.getString("access_token")!!
        val refreshToken = refreshToken(accessToken)["response"]!!.jsonObject

        return Response.Success(refreshToken.getString("token")!!)
    }

    private suspend fun refreshToken(accessToken: String): JsonObject {
        val params = Parameters.build {
            append("access_token", accessToken)
            append("receipt", VkUserClient.RECEIPT)
            append("v", VK_API_VERSION)
        }

        val responseString = httpClient.get(BASE_API_URL + "auth.refreshToken") {
            url.parameters.appendAll(params)
            headers.appendAll(VkUserClient.HEADER)
        }

        val responseParsed = json.parseToJsonElement(responseString).jsonObject

        if ("error" in responseParsed.toString()) {
            throw AuthException("Error: ${responseParsed["error_description"]}")
        }

        return responseParsed
    }

    private suspend fun passTwoFactor(twoFactor: TwoFactor) {
        val parameters = Parameters.build {
            append("act", "a_authcheck_code")
            append("al", "1")
            append("code", twoFactor.code)
            append("remember", twoFactor.remember.toInt())
            append("hash", twoFactor.authHash)
        }

        val resp = httpClient.post("https://vk.com/al_login.php") {
            headers.appendAll(VkUserClient.HEADER)
            body = FormDataContent(parameters)
        }

        val data = resp.replace("[-]".toRegex(), "").toJsonObject()
        val status = data.getJsonArray("payload")!!.getInt(0)

        when (status) {
            4 -> {
                val path = data.getJsonArray("payload")!!.getJsonArray(1).getString(0).replace("[\\\\\"]".toRegex(), "")
                httpClient.get("https://vk.com/$path")
            }

            in listOf(0, 8) -> return passTwoFactor(twoFactor)

            2 -> throw TwoFactorException("ReCaptcha required")
        }

        throw TwoFactorException("Two factor authentication failed")
    }

    /**
     * VK Authentication with getting cookies remixsid

     * @param captcha Captcha object
     * @return token
     */
    suspend fun vkLogin(
        captcha: CaptchaObject? = null,
        twoFactor: TwoFactor? = null
    ): Response {
        log.info("Logging in...")

        var response = httpClient.get("https://vk.com/") {
            userAgent(STRING_USER_AGENT)
        }

        val params = Parameters.build {
            append("act", "login")
            append("role", "al_frame")
            append("_origin", "https://vk.com")
            append("utf8", 1)
            append("email", login)
            append("pass", password)
            append("lg_h", regexSearch(RE_LOGIN_HASH, response, 1)!!)

            if (captcha != null) {
                log.info("Using captcha code: ${captcha.captchaSid}: ${captcha.captchaCode}")

                append("captcha_sid", captcha.captchaSid)
                append("captcha_key", captcha.captchaCode)
            }
        }

        response = httpClient.post("https://login.vk.com/") {
            body = FormDataContent(params)
        }

        when {
            "act=authcheck" in response -> {
                log.info("Two Factor is required")

                response = httpClient.get("https://vk.com/login?act=authcheck")
                val authHash = regexSearch(RE_AUTH_HASH, response, 1)!!

                if (twoFactor != null) {
                    passTwoFactor(twoFactor)
                } else {
                    return Response.TwoFactor(authHash)
                }
            }

            "onLoginCaptcha(" in response -> {
                log.info("Captcha code is required")

                val sid = regexSearch(RE_CAPTCHA_ID, response)!!
                val url = "https://api.vk.com/captcha.php?sid=$sid"

                return Response.Captcha(sid, url)
            }

            "onLoginReCaptcha(" in response -> {
                log.info("Captcha code is required (recaptcha)")

                val sid = System.currentTimeMillis().toString()
                val url = "https://api.vk.com/captcha.php?sid=$sid"

                return Response.Captcha(sid, url)
            }

            "onLoginFailed(4" in response -> return Response.Error(ErrorType.INVALID_CLIENT)

            else -> {
                if (checkSid()) log.info("Got remixsid")

                else return Response.Error(ErrorType.UNKNOWN)
            }
        }

        return apiLogin()
    }

    /**
     * Получение токена через Desktop приложение
     * @return token
     */
    private suspend fun apiLogin(): Response {
        for (cookie in listOf("p", "l")) {
            if (httpClient.cookies("login.vk.com").none { it.name == cookie }) {
                throw AuthException("API auth error (no login cookies)")
            }
        }

        val params = Parameters.build {
            append("client_id", appId)
            append("scope", scope.mask)
            append("response_type", "token")
            append("redirect_url", redirectUrl)
        }

        var response = httpClient.get("https://oauth.vk.com/authorize") {
            userAgent(STRING_USER_AGENT)
            url.parameters.appendAll(params)
        }

        var responseString = response.receive()

        if ("act=blocked" in response.request.url.toString()) {
            return Response.Error(ErrorType.ACCOUNT_BLOCKED)
        }

        if ("access_token" !in response.request.url.toString()) {
            val url = regexSearch(RE_TOKEN_URL, responseString, 1)

            if (url != null) {
                response = httpClient.get(url) {
                    userAgent(STRING_USER_AGENT)
                }

                responseString = response.receive()
            }
        }

        when {
            "access_token" in response.request.url.toString() -> {
                val parts = response.request.url.toString()
                    .split("#", limit = 2)[1]
                    .split("&").subList(0, 1)

                val token = parts[0].split("=")[1]

                log.info("Authentication succeeded!")

                return Response.Success(token)
            }

            "oauth.vk.com/error" in response.request.url.toString() -> {
                val errorData = responseString.toJsonObject()
                var errorText = errorData.getString("error_description")!!

                if ("@vk.com" in errorText) {
                    errorText = errorData.getString("error")!!
                }

                throw AuthException("VK auth error: $errorText")
            }

            else -> return Response.Error(ErrorType.UNKNOWN)
        }
    }

    private suspend fun checkSid(): Boolean {
        return httpClient.cookies("").any { it.name == "remixsid" || it.name == "remixsid6" }
    }

    sealed class Response {
        data class Error(val errorType: ErrorType) : Response()
        data class Captcha(val sid: String, val url: String) : Response()
        data class TwoFactor(val hash: String) : Response()
        data class Success(val token: String) : Response()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy