Skip to content

Commit

Permalink
feat: implement JWT tokens by default, T1 are still available with 'G…
Browse files Browse the repository at this point in the history
…enerateT1Token'
  • Loading branch information
Tr00d committed Nov 7, 2024
1 parent 4b28fa5 commit c5485f7
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 13 deletions.
51 changes: 51 additions & 0 deletions OpenTok/OpenTok.cs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,57 @@ public string GenerateToken(string sessionId, Role role = Role.PUBLISHER, double
Session session = new Session(sessionId, ApiKey, ApiSecret);
return session.GenerateToken(role, expireTime, data, initialLayoutClassList);
}

/// <summary>
/// Creates a token for connecting to an OpenTok session. In order to authenticate a user
/// connecting to an OpenTok session, the client passes a token when connecting to the session.
/// <para>
/// For testing, you can also generate test tokens by logging in to your
/// <a href="https://tokbox.com/account">TokBox account</a>.
/// </para>
/// </summary>
/// <param name="sessionId">
/// The session ID corresponding to the session to which the user will connect.
/// </param>
/// <param name="role">
/// The role for the token. Valid values are defined in the Role enum:
/// - <see cref="Role.SUBSCRIBER"/> (A subscriber can only subscribe to streams)
/// - <see cref="Role.PUBLISHER"/> (A publisher can publish streams, subscribe to streams, and signal.
/// (This is the default value if you do not specify a role.))
/// - <see cref="Role.MODERATOR"/> (In addition to the privileges granted to a publisher,
/// a moderator can perform moderation functions, such as forcing clients
/// to disconnect, to stop publishing streams, or to mute audio in published streams. See the
/// <a href="https://tokbox.com/developer/guides/moderation/">Moderation developer guide</a>.
/// </param>
/// <param name="expireTime">
/// The expiration time of the token, in seconds since the UNIX epoch. Pass in 0 to use the default
/// expiration time of 24 hours after the token creation time. The maximum expiration time is 30 days
/// after the creation time.
/// </param>
/// <param name="data">
/// A string containing connection metadata describing the end-user. For example, you can pass the
/// user ID, name, or other data describing the end-user. The length of the string is limited to 1000
/// characters. This data cannot be updated once it is set.
/// </param>
/// <param name="initialLayoutClassList">
/// A list of strings values containing the initial layout for the stream.
/// </param>
/// <returns></returns>
public string GenerateT1Token(string sessionId, Role role = Role.PUBLISHER, double expireTime = 0, string data = null, List<string> initialLayoutClassList = null)
{
if (String.IsNullOrEmpty(sessionId))
{
throw new OpenTokArgumentException("Session id cannot be empty or null");
}

if (!OpenTokUtils.ValidateSession(sessionId))
{
throw new OpenTokArgumentException("Invalid Session id " + sessionId);
}

Session session = new Session(sessionId, ApiKey, ApiSecret);
return session.GenerateT1Token(role, expireTime, data, initialLayoutClassList);
}

/// <summary>
/// Starts archiving an OpenTok session.
Expand Down
1 change: 1 addition & 0 deletions OpenTok/OpenTok.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Enums.NET" Version="4.0.0"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.0" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
Expand Down
44 changes: 42 additions & 2 deletions OpenTok/Session.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

using System.Web;

using Microsoft.IdentityModel.Tokens;
using OpenTokSDK.Util;
using OpenTokSDK.Exception;

Expand Down Expand Up @@ -131,14 +133,52 @@ internal Session(string sessionId, int apiKey, string apiSecret, string location
/// </param>
/// <param name="initialLayoutClassList"></param>
/// <returns>The token string.</returns>
public string GenerateToken(Role role = Role.PUBLISHER, double expireTime = 0, string data = null, List<string> initialLayoutClassList = null)
public string GenerateT1Token(Role role = Role.PUBLISHER, double expireTime = 0, string data = null, List<string> initialLayoutClassList = null)
{
double createTime = OpenTokUtils.GetCurrentUnixTimeStamp();
int nonce = OpenTokUtils.GetRandomNumber();

string dataString = BuildDataString(role, expireTime, data, createTime, nonce, initialLayoutClassList);
return BuildTokenString(dataString);
}

/// <summary>
/// Creates a token for connecting to an OpenTok session. In order to authenticate a user
/// connecting to an OpenTok session that user must pass an authentication token along with
/// the API key.
/// </summary>
/// <param name="role">
/// The role for the token. Valid values are defined in the Role enum:
/// - <see cref="Role.SUBSCRIBER"/> A subscriber can only subscribe to streams.
/// - <see cref="Role.PUBLISHER"/> A publisher can publish streams, subscribe to
/// streams, and signal. (This is the default value if you do not specify a role.)
/// - <see cref="Role.MODERATOR"/> In addition to the privileges granted to a
/// publisher, in clients using the OpenTok.js library, a moderator can call the
/// forceUnpublish() and forceDisconnect() method of the Session object.
/// </param>
/// <param name="expireTime">
/// The expiration time of the token, in seconds since the UNIX epoch.
/// Pass in 0 to use the default expiration time of 24 hours after the token creation time.
/// The maximum expiration time is 30 days after the creation time.
/// </param>
/// <param name="data">
/// A string containing connection metadata describing the end-user. For example,
/// you can pass the user ID, name, or other data describing the end-user. The length of the
/// string is limited to 1000 characters. This data cannot be updated once it is set.
/// </param>
/// <param name="initialLayoutClassList"></param>
/// <returns>The token string.</returns>
public string GenerateToken(Role role = Role.PUBLISHER, double expireTime = 0, string data = null, List<string> initialLayoutClassList = null) =>
new TokenGenerator().GenerateToken(new TokenData()
{
ApiSecret = this.ApiSecret,
Role = role,
ApiKey = this.ApiKey.ToString(),
Data = data,
SessionId = this.Id,
ExpireTime = expireTime,
InitialLayoutClasses = initialLayoutClassList ?? Enumerable.Empty<string>(),
});

private string BuildTokenString(string dataString)
{
Expand Down
37 changes: 37 additions & 0 deletions OpenTok/Util/HttpClient.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using OpenTokSDK.Constants;
using OpenTokSDK.Exception;
Expand Down Expand Up @@ -397,6 +401,39 @@ private string GenerateJwt(int key, string secret, int expiryPeriod = 300)
var token = encoder.Encode(payload, secret);
return token;
}

private string GenerateJwt2(int apiKey, string apiSecret, int expiryPeriod = 300)
{
var tokenData = new byte[64];
var rng = RandomNumberGenerator.Create();
rng.GetBytes(tokenData);
var jwtTokenId = Convert.ToBase64String(tokenData);
var payload = new Dictionary<string, object>
{
{"iss", apiKey},
{"ist", "project"},
{"role", "publisher"},
{"session_id", "sessionid"},
{"scope", "session.connect"},
{"iat", (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds},
{"exp", (long) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + 300},
{"jti", jwtTokenId},
{"initial_layout_list", ""},
{"nonce", OpenTokUtils.GetRandomNumber()},
};
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(apiSecret);
var tokenDescriptor = new SecurityTokenDescriptor
{
//Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256),
Claims = payload,
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var a = tokenHandler.WriteToken(token);
return a;
}

private Dictionary<string, string> GetCommonHeaders()
{
Expand Down
2 changes: 1 addition & 1 deletion OpenTok/Util/OpenTokUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public static string Convert64(string input)
}
public static double GetUnixTimeStampForDate(DateTime date)
{
return (date - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
return (date.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
}

public static double GetCurrentUnixTimeStamp()
Expand Down
88 changes: 88 additions & 0 deletions OpenTok/Util/TokenGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#region

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using OpenTokSDK.Exception;

#endregion

namespace OpenTokSDK.Util
{
internal class TokenGenerator
{
public string GenerateToken(TokenData data)
{
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
SigningCredentials = data.GenerateCredentials(),
Claims = data.GeneratePayload(),
Expires = data.GetExpireTime().UtcDateTime
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}

internal struct TokenData
{
public string ApiKey { get; set; }
public string ApiSecret { get; set; }
public string SessionId { get; set; }
public Role Role { get; set; }
public double ExpireTime { get; set; }
public string Data { get; set; }
public IEnumerable<string> InitialLayoutClasses { get; set; }

public DateTimeOffset GetExpireTime()
{
return CheckExpireTime(ExpireTime, new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds())
? DateTimeOffset.FromUnixTimeSeconds((long)ExpireTime)
: new DateTimeOffset(DateTime.UtcNow.AddSeconds(300));
}

internal Dictionary<string, object> GeneratePayload()
{
var creationTime = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
var payload = new Dictionary<string, object>
{
{ "iss", ApiKey },
{ "ist", "project" },
{ "role", Role.ToString().ToLowerInvariant() },
{ "session_id", SessionId },
{ "scope", "session.connect" },
{ "iat", creationTime },
{ "jti", GenerateTokenId() },
{ "initial_layout_list", string.Join(" ", InitialLayoutClasses) },
{ "nonce", OpenTokUtils.GetRandomNumber() }
};

return payload;
}

private static bool CheckExpireTime(double expireTime, double createTime)
{
if (expireTime == 0) return false;
if (expireTime > createTime && expireTime <= OpenTokUtils.GetCurrentUnixTimeStamp() + 2592000) return true;
throw new OpenTokArgumentException(
$"Invalid expiration time for token {expireTime}. Expiration time has to be positive and less than 30 days");
}

private static string GenerateTokenId()
{
var tokenData = new byte[64];
RandomNumberGenerator.Create().GetBytes(tokenData);
return Convert.ToBase64String(tokenData);
}

internal SigningCredentials GenerateCredentials()
{
var key = Encoding.ASCII.GetBytes(ApiSecret);
return new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256);
}
}
}
1 change: 1 addition & 0 deletions OpenTokTest/OpenTokTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@
<Reference Include="Microsoft.CSharp" />
<PackageReference Include="AutoFixture" Version="4.18.0" />
<PackageReference Include="Enums.NET" Version="4.0.1" />
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
Expand Down
Loading

0 comments on commit c5485f7

Please sign in to comment.