Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic token refresh #39

Open
RaynoldVanHeyningen opened this issue Jun 9, 2024 · 4 comments
Open

Automatic token refresh #39

RaynoldVanHeyningen opened this issue Jun 9, 2024 · 4 comments

Comments

@RaynoldVanHeyningen
Copy link

I am running the Suno API properly, however i notice every 7 days, i have to update my session id and cookie value.

In the readme it says: "Automatic token maintenance and keep-alive"
How can i make sure my token gets properly refreshed, without me having to manually update this information?

@FergaliciousPixelicious

@RaynoldVanHeyningen in case you're interested, check out sumosuno.com

They are able to keep the token alive and it's a plug and play option in case you're interested

@RaynoldVanHeyningen
Copy link
Author

RaynoldVanHeyningen commented Jun 26, 2024

@FergaliciousPixelicious Thanks, but this is not open-source, it's managed payware, so completely unrelated to this project.

I've built my own solution.

@jtoy
Copy link

jtoy commented Jul 2, 2024

@RaynoldVanHeyningen what is your solution?

@RaynoldVanHeyningen
Copy link
Author

@RaynoldVanHeyningen what is your solution?

i noticed there was a form of token refresh going on, the main in problem 'in my situation' was that i was running the SunoAPI in a serverless environment, so the refreshed token wasn't persisted.

Since i'm running in Azure and using .NET for my application, i rebuild this Suno API solution into a custom .NET solution, hosted on Azure, with the main difference being that when a unauthorized request happens in the api, i refresh the session_id and cookie and store these in a redis cache. Afterwards the request will be executed again with the refreshed information.

Here is the relevant code, but as i said this is in .NET:

SunoCookieService.cs:

public class SunoCookieService : BackgroundService
{
    private readonly IHttpClientFactory _clientFactory;
    private readonly IConfiguration _configuration;
    private readonly ILogger<SunoCookieService> _logger;
    private readonly IDistributedCache _cache;
    public SunoCookie SunoAuth { get; }
    private DateTime _tokenExpiryTime;

    public SunoCookieService(IHttpClientFactory clientFactory, IConfiguration configuration, ILogger<SunoCookieService> logger, IDistributedCache cache)
    {
        _clientFactory = clientFactory;
        _configuration = configuration;
        _logger = logger;
        _cache = cache;
        SunoAuth = new SunoCookie();
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation($"------------------------------------------------------------------");
        _logger.LogInformation($"ExecuteAsync");

        await InitializeFromCache();

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                if (DateTime.UtcNow >= _tokenExpiryTime)
                {
                    await UpdateTokenAndSession();
                }
                else
                {
                    await TouchSession();
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error updating token or touching session");
            }

            await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
        }
    }

    private async Task InitializeFromCache()
    {
        _logger.LogInformation($"------------------------------------------------------------------");
        _logger.LogInformation($"InitializeFromCache");
        
        var sessionId = await _cache.GetStringAsync("Suno:SessionId");
        var cookie = await _cache.GetStringAsync("Suno:Cookie");
        var token = await _cache.GetStringAsync("Suno:Token");
        var tokenExpiry = await _cache.GetStringAsync("Suno:TokenExpiry");

        if (string.IsNullOrEmpty(sessionId) || string.IsNullOrEmpty(cookie))
        {
            sessionId = _configuration["SunoAPI:SessionId"];
            cookie = _configuration["SunoAPI:Cookie"];
            
            if (string.IsNullOrEmpty(sessionId) || string.IsNullOrEmpty(cookie))
            {
                throw new InvalidOperationException("SessionId and Cookie must be provided either in cache or configuration.");
            }
            await _cache.SetStringAsync("Suno:SessionId", sessionId);
            await _cache.SetStringAsync("Suno:Cookie", cookie);
        }
        else
        {
            _logger.LogInformation("Initialized from cache");
        }

        SunoAuth.SetSessionId(sessionId);
        SunoAuth.LoadCookie(cookie);
        
        if (!string.IsNullOrEmpty(token))
        {
            SunoAuth.SetToken(token);
            if (DateTime.TryParse(tokenExpiry, out var expiry))
            {
                _tokenExpiryTime = expiry;
            }
            _logger.LogInformation($"Initialized with cached token: {token}, expiry time: {_tokenExpiryTime}");
        }
    }

    public async Task UpdateTokenAndSession()
    {
        _logger.LogInformation($"------------------------------------------------------------------");
        _logger.LogInformation($"UpdateTokenAndSession");
        
        var client = _clientFactory.CreateClient();
        client.DefaultRequestHeaders.Add("Cookie", SunoAuth.GetCookie());

        var response = await client.PostAsync(
            $"https://clerk.suno.com/v1/client/sessions/{SunoAuth.SessionId}/tokens?_clerk_js_version=4.72.0-snapshot.vc141245",
            null);

        if (!response.IsSuccessStatusCode)
        {
            _logger.LogWarning("Token update failed with status code {StatusCode}. Attempting to refresh session.", response.StatusCode);
            await RefreshSession(client);
            return;
        }

        var setCookie = response.Headers.GetValues("Set-Cookie").FirstOrDefault();
        if (!string.IsNullOrEmpty(setCookie))
        {
            SunoAuth.LoadCookie(setCookie);
            await _cache.SetStringAsync("Suno:Cookie", setCookie);
        }

        var responseContent = await response.Content.ReadAsStringAsync();
        var jsonResponse = JObject.Parse(responseContent);
        var token = jsonResponse["jwt"]?.ToString();
        var sessionId = ExtractSessionIdFromToken(token);

        if (!string.IsNullOrEmpty(token) && !string.IsNullOrEmpty(sessionId))
        {
            SunoAuth.SetToken(token);
            SunoAuth.SetSessionId(sessionId);
            await _cache.SetStringAsync("Suno:Token", token);
            await _cache.SetStringAsync("Suno:SessionId", sessionId);

            // Set token expiry time assuming token expiry time is 30 minutes from now
            _tokenExpiryTime = DateTime.UtcNow.AddMinutes(30); // Adjust according to actual token lifetime
            await _cache.SetStringAsync("Suno:TokenExpiry", _tokenExpiryTime.ToString());

            _logger.LogInformation($"Token updated successfully: {token}, new session ID: {sessionId}, new expiry time: {_tokenExpiryTime}");
        }
    }

    private async Task TouchSession()
    {
        _logger.LogInformation($"------------------------------------------------------------------");
        _logger.LogInformation($"TouchSession");
        
        var client = _clientFactory.CreateClient();
        client.DefaultRequestHeaders.Add("Cookie", SunoAuth.GetCookie());

        var response = await client.PostAsync(
            $"https://clerk.suno.com/v1/client/sessions/{SunoAuth.SessionId}/touch?_clerk_js_version=4.72.0-snapshot.vc141245",
            null);

        if (!response.IsSuccessStatusCode)
        {
            _logger.LogWarning("Touch session failed with status code {StatusCode}.", response.StatusCode);
            return;
        }

        var responseContent = await response.Content.ReadAsStringAsync();
        var jsonResponse = JObject.Parse(responseContent);
        var session = jsonResponse["response"]?["id"]?.ToString();

        if (!string.IsNullOrEmpty(session))
        {
            _logger.LogInformation($"Session touched successfully: {session}");
        }
    }

    private string ExtractSessionIdFromToken(string token)
    {
        _logger.LogInformation($"------------------------------------------------------------------");
        _logger.LogInformation($"ExtractSessionIdFromToken");
        
        if (string.IsNullOrEmpty(token)) return null;

        var parts = token.Split('.');
        if (parts.Length != 3) return null;

        var payload = parts[1];
        var json = Base64UrlDecode(payload);
        var jwtPayload = JObject.Parse(json);
        return jwtPayload["sid"]?.ToString();
    }

    private static string Base64UrlDecode(string input)
    {
        input = input.Replace('-', '+').Replace('_', '/');
        switch (input.Length % 4)
        {
            case 2: input += "=="; break;
            case 3: input += "="; break;
        }
        var bytes = Convert.FromBase64String(input);
        return Encoding.UTF8.GetString(bytes);
    }

    private async Task RefreshSession(HttpClient client)
    {
        _logger.LogInformation($"------------------------------------------------------------------");
        _logger.LogInformation($"RefreshSession");
        _logger.LogWarning("Session expired. Attempting to refresh...");

        var newSessionId = _configuration["SunoAPI:SessionId"];
        var newCookie = _configuration["SunoAPI:Cookie"];

        SunoAuth.SetSessionId(newSessionId);
        SunoAuth.LoadCookie(newCookie);

        await _cache.SetStringAsync("Suno:SessionId", newSessionId);
        await _cache.SetStringAsync("Suno:Cookie", newCookie);

        _logger.LogInformation("Session refreshed successfully");

        await UpdateTokenAndSession();  // Attempt to update the token again with the new session
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants