Initial push

This commit is contained in:
2021-02-25 00:51:33 +01:00
parent 13844c3e93
commit 1525e40378
16 changed files with 1508 additions and 0 deletions

115
siwa/api/siwa.script_api Normal file
View File

@@ -0,0 +1,115 @@
- name: siwa
type: table
desc: Functions and constants for interacting Sign in with Apple.
[icon:ios]
members:
#*****************************************************************************************************
- name: is_supported
type: function
desc: Check if Sign in with Apple is available (iOS 13+).
#*****************************************************************************************************
- name: get_credential_state
type: function
desc: Get the credential state of a user.
parameters:
- name: user_id
type: string
desc: User id to get credential state for.
- name: callback
type: function
desc: Credential state callback function.
parameters:
- name: self
type: object
desc: The current object.
- name: state
type: table
desc: The credential state (user_id, credential_state)
examples:
- desc: |-
```lua
siwa.get_credential_state(id, function(self, data)
if data.credential_state == siwa.STATE_AUTHORIZED then
print("User has still authorized the application", data.user_id)
elseif data.credential_state == siwa.STATE_REVOKED then
print("User has revoked authorization for the application", data.user_id)
end
end)
```
#*****************************************************************************************************
- name: authenticate
type: function
desc: Show the Sign in with Apple UI
parameters:
- name: callback
type: function
desc: Authentication callback function.
parameters:
- name: self
type: object
desc: The current object.
- name: state
type: table
desc: The authentication result data (user_id, identity_token, email, first_name, family_name, status, result)
examples:
- desc: |-
```lua
siwa.authenticate(function(self, data)
print(data.identity_token)
print(data.user_id)
print(data.first_name, data.family_name)
print(data.email)
if data.user_status == siwa.STATUS_LIKELY_REAL then
print("Likely a real person")
end
end)
```
#*****************************************************************************************************
- name: STATE_NOT_FOUND
type: number
desc: The user cant be found.
- name: STATE_UNKNOWN
type: number
desc: Unknown credential state.
- name: STATE_AUTHORIZED
type: number
desc: The user is authorized.
- name: STATE_REVOKED
type: number
desc: Authorization for the given user has been revoked.
- name: STATUS_UNKNOWN
type: number
desc: The system hasnt determined whether the user might be a real person.
- name: STATUS_UNSUPPORTED
type: number
desc: The system cant determine this users status as a real person.
- name: STATUS_LIKELY_REAL
type: number
desc: The user appears to be a real person.

30
siwa/ext.manifest Executable file
View File

@@ -0,0 +1,30 @@
name: siwa
platforms:
x86-osx:
context:
frameworks: []
flags: ["-std=c++11", "-stdlib=libc++"]
linkFlags: ["-stdlib=libc++"]
libs: ["c++"]
x86_64-osx:
context:
frameworks: []
flags: ["-std=c++11", "-stdlib=libc++"]
libs: ["c++"]
x86_64-ios:
context:
frameworks: ['AuthenticationServices']
defines: ['SUPPORTS_SIWA']
armv7-ios:
context:
frameworks: ['AuthenticationServices']
defines: ['SUPPORTS_SIWA']
arm64-ios:
context:
frameworks: ['AuthenticationServices']
defines: ['SUPPORTS_SIWA']

292
siwa/src/siwa.cpp Executable file
View File

@@ -0,0 +1,292 @@
#if defined(DM_PLATFORM_IOS)
#include "siwa.h"
#include <dmsdk/sdk.h>
#define MODULE_NAME "siwa"
SiwaData g_SiwaData;
SiwaCallbackData g_SiwaCallbackData;
char* Siwa_GetUserId()
{
return g_SiwaData.m_userID;
}
static void Siwa_ResetCallbackData()
{
free(g_SiwaCallbackData.m_userID);
g_SiwaCallbackData.m_userID = 0;
free(g_SiwaCallbackData.m_identityToken);
g_SiwaCallbackData.m_identityToken = 0;
free(g_SiwaCallbackData.m_userID);
g_SiwaCallbackData.m_userID = 0;
free(g_SiwaCallbackData.m_email);
g_SiwaCallbackData.m_email = 0;
free(g_SiwaCallbackData.m_firstName);
g_SiwaCallbackData.m_firstName = 0;
free(g_SiwaCallbackData.m_familyName);
g_SiwaCallbackData.m_familyName = 0;
free(g_SiwaCallbackData.m_identityToken);
g_SiwaCallbackData.m_identityToken = 0;
free(g_SiwaCallbackData.m_message);
g_SiwaCallbackData.m_message = 0;
g_SiwaCallbackData.m_userStatus = STATUS_UNSUPPORTED;
g_SiwaCallbackData.m_state = STATE_UNKNOWN;
g_SiwaCallbackData.m_cmd = CMD_NONE;
}
void Siwa_QueueCredentialCallback(const char* userID, const SiwaCredentialState state)
{
if(g_SiwaCallbackData.m_cmd != CMD_NONE) {
dmLogError("Can't queue credential callback, already have a callback queued!");
return;
}
g_SiwaCallbackData.m_cmd = CMD_CREDENTIAL;
g_SiwaCallbackData.m_userID = strdup(userID);
g_SiwaCallbackData.m_state = state;
}
void Siwa_QueueAuthSuccessCallback(const char* identityToken, const char* userID, const char* email, const char* firstName, const char* familyName, const SiwaUserDetectionStatus userStatus)
{
if(g_SiwaCallbackData.m_cmd != CMD_NONE) {
dmLogError("Can't queue auth success callback, already have a callback queued!");
return;
}
g_SiwaCallbackData.m_cmd = CMD_AUTH_SUCCESS;
g_SiwaCallbackData.m_identityToken = strdup(identityToken);
g_SiwaCallbackData.m_userID = strdup(userID);
g_SiwaCallbackData.m_email = strdup(email != 0 ? email: "");
g_SiwaCallbackData.m_firstName = strdup(firstName != 0 ? firstName: "");
g_SiwaCallbackData.m_familyName = strdup(familyName != 0 ? familyName : "");
g_SiwaCallbackData.m_userStatus = userStatus;
g_SiwaCallbackData.m_message = strdup("");
}
void Siwa_QueueAuthFailureCallback(const char* message)
{
if(g_SiwaCallbackData.m_cmd != CMD_NONE) {
dmLogError("Can't queue auth error callback, already have a callback queued!");
return;
}
g_SiwaCallbackData.m_cmd = CMD_AUTH_FAILED;
g_SiwaCallbackData.m_message = strdup(message);
}
static void Siwa_TriggerCallback()
{
lua_State* L = dmScript::GetCallbackLuaContext(g_SiwaData.m_callback);
DM_LUA_STACK_CHECK(L, 0);
if (dmScript::SetupCallback(g_SiwaData.m_callback))
{
lua_createtable(L, 0, 3);
if (g_SiwaCallbackData.m_cmd == CMD_CREDENTIAL)
{
lua_pushstring(L, "result");
lua_pushstring(L, "SUCCESS");
lua_settable(L, -3);
lua_pushstring(L, "user_id");
lua_pushstring(L, g_SiwaCallbackData.m_userID);
lua_settable(L, -3);
lua_pushstring(L, "credential_state");
lua_pushnumber(L, g_SiwaCallbackData.m_state);
lua_settable(L, -3);
}
else if (g_SiwaCallbackData.m_cmd == CMD_AUTH_SUCCESS)
{
lua_pushstring(L, "result");
lua_pushstring(L, "SUCCESS");
lua_settable(L, -3);
lua_pushstring(L, "identity_token");
lua_pushstring(L, g_SiwaCallbackData.m_identityToken);
lua_settable(L, -3);
lua_pushstring(L, "user_id");
lua_pushstring(L, g_SiwaCallbackData.m_userID);
lua_settable(L, -3);
lua_pushstring(L, "email");
lua_pushstring(L, g_SiwaCallbackData.m_email);
lua_settable(L, -3);
lua_pushstring(L, "first_name");
lua_pushstring(L, g_SiwaCallbackData.m_firstName);
lua_settable(L, -3);
lua_pushstring(L, "family_name");
lua_pushstring(L, g_SiwaCallbackData.m_familyName);
lua_settable(L, -3);
lua_pushstring(L, "user_status");
lua_pushnumber(L, g_SiwaCallbackData.m_userStatus);
lua_settable(L, -3);
}
else if (g_SiwaCallbackData.m_cmd == CMD_AUTH_FAILED)
{
lua_pushstring(L, "result");
lua_pushstring(L, "ERROR");
lua_settable(L, -3);
lua_pushstring(L, "message");
lua_pushstring(L, g_SiwaCallbackData.m_message);
lua_settable(L, -3);
}
if (lua_pcall(L, 2, 0, 0) != 0)
{
dmLogError("Error running siwa callback: %s", lua_tostring(L, -1));
lua_pop(L, 1);
}
dmScript::TeardownCallback(g_SiwaData.m_callback);
}
}
static void Siwa_SetupCallback(lua_State* L, int index)
{
if (g_SiwaData.m_callback) {
dmScript::DestroyCallback(g_SiwaData.m_callback);
}
g_SiwaData.m_callback = dmScript::CreateCallback(L, index);
}
static void Siwa_CleanupCallback() {
if (g_SiwaData.m_callback) {
dmScript::DestroyCallback(g_SiwaData.m_callback);
g_SiwaData.m_callback = 0;
}
}
static int Siwa_GetCredentialState(lua_State* L){
DM_LUA_STACK_CHECK(L, 1);
if (!Siwa_PlatformIsSupported()) {
dmLogWarning("Sign in with Apple is not available");
lua_pushboolean(L, 0);
return 1;
}
if(g_SiwaData.m_callback != 0)
{
dmLogError("Callback already in progress");
lua_pushboolean(L, 0);
return 1;
}
luaL_checktype(L, 1, LUA_TSTRING);
if (g_SiwaData.m_userID) free(g_SiwaData.m_userID);
g_SiwaData.m_userID = strdup(lua_tostring(L, 1));
luaL_checktype(L, 2, LUA_TFUNCTION);
Siwa_SetupCallback(L, 2);
Siwa_PlatformGetCredentialState();
lua_pushboolean(L, 1);
return 1;
}
static int Siwa_AuthenticateWithApple(lua_State* L) {
DM_LUA_STACK_CHECK(L, 1);
if (!Siwa_PlatformIsSupported()) {
dmLogWarning("Sign in with Apple is not available");
lua_pushboolean(L, 0);
return 1;
}
if(g_SiwaData.m_callback != 0)
{
dmLogError("Callback already in progress");
lua_pushboolean(L, 0);
return 1;
}
luaL_checktype(L, 1, LUA_TFUNCTION);
Siwa_SetupCallback(L, 1);
Siwa_PlatformAuthenticateWithApple();
lua_pushboolean(L, 1);
return 1;
}
static int Siwa_IsSupported(lua_State* L) {
DM_LUA_STACK_CHECK(L, 1);
lua_pushboolean(L, Siwa_PlatformIsSupported());
return 1;
}
static dmExtension::Result SiwaAppInitialize(dmExtension::AppParams* params)
{
Siwa_ResetCallbackData();
return dmExtension::RESULT_OK;
}
static dmExtension::Result SiwaAppFinalize(dmExtension::AppParams* params)
{
return dmExtension::RESULT_OK;
}
const luaL_reg lua_register[] =
{
{"is_supported", Siwa_IsSupported},
{"get_credential_state", Siwa_GetCredentialState},
{"authenticate", Siwa_AuthenticateWithApple},
{0, 0}
};
static dmExtension::Result SiwaInitialize(dmExtension::Params* params)
{
lua_State* L = params->m_L;
int top = lua_gettop(L);
luaL_register(L, MODULE_NAME, lua_register);
#define SETCONSTANT(name) \
lua_pushnumber(L, (lua_Number) name); \
lua_setfield(L, -2, #name);\
SETCONSTANT(STATE_NOT_FOUND)
SETCONSTANT(STATE_UNKNOWN)
SETCONSTANT(STATE_AUTHORIZED)
SETCONSTANT(STATE_REVOKED)
SETCONSTANT(STATUS_UNKNOWN)
SETCONSTANT(STATUS_UNSUPPORTED)
SETCONSTANT(STATUS_LIKELY_REAL)
#undef SETCONSTANT
lua_pop(L, 1);
assert(top == lua_gettop(L));
return dmExtension::RESULT_OK;
}
static dmExtension::Result SiwaUpdate(dmExtension::Params* params)
{
if(g_SiwaCallbackData.m_cmd != CMD_NONE)
{
Siwa_TriggerCallback();
Siwa_ResetCallbackData();
Siwa_CleanupCallback();
}
return dmExtension::RESULT_OK;
}
static dmExtension::Result SiwaFinalize(dmExtension::Params* params)
{
return dmExtension::RESULT_OK;
}
DM_DECLARE_EXTENSION(siwa, MODULE_NAME, SiwaAppInitialize, SiwaAppFinalize, SiwaInitialize, SiwaUpdate, 0, SiwaFinalize);
#endif

86
siwa/src/siwa.h Executable file
View File

@@ -0,0 +1,86 @@
#pragma once
#if defined(DM_PLATFORM_IOS)
#include <dmsdk/sdk.h>
enum SiwaCallbackCmd
{
CMD_NONE = 0,
CMD_CREDENTIAL = 1,
CMD_AUTH_SUCCESS = 2,
CMD_AUTH_FAILED = 3
};
// https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovidercredentialstate/asauthorizationappleidprovidercredentialauthorized?language=objc
enum SiwaCredentialState
{
STATE_UNKNOWN = 0,
STATE_AUTHORIZED = 1,
STATE_REVOKED = 2,
STATE_NOT_FOUND = 3
};
// https://developer.apple.com/documentation/authenticationservices/asuserdetectionstatus?language=objc
enum SiwaUserDetectionStatus
{
STATUS_UNSUPPORTED = 0,
STATUS_LIKELY_REAL = 1,
STATUS_UNKNOWN = 2
};
struct SiwaCallbackData
{
SiwaCallbackData()
{
memset(this, 0, sizeof(*this));
};
SiwaCallbackCmd m_cmd;
SiwaCredentialState m_state;
SiwaUserDetectionStatus m_userStatus;
char* m_identityToken;
char* m_userID;
char* m_email;
char* m_firstName;
char* m_familyName;
char* m_message;
};
struct SiwaData
{
SiwaData()
{
memset(this, 0, sizeof(*this));
};
// the user ID used for checking credential state
char* m_userID;
dmScript::LuaCallbackInfo* m_callback;
};
char* Siwa_GetUserId();
// Queue the credential check callback to be triggered next update call in the main thread.
void Siwa_QueueCredentialCallback(const char* userID, const SiwaCredentialState state);
// Queue the sign in authorization callback to be triggered next update call in the main thread, when authorization succeeds.
void Siwa_QueueAuthSuccessCallback(const char* identityToken, const char* userID, const char* email, const char* firstName, const char* familyName, const SiwaUserDetectionStatus userStatus);
// Queue the sign in authorization callback to be triggered next update call in the main thread, when authorization fails.
void Siwa_QueueAuthFailureCallback(const char* message);
// Trigged by a call from lua to check if sign in with apple is supported on this device.
bool Siwa_PlatformIsSupported();
// Triggered by a call from lua to start the sign in with apple flow
// expects the callback to be a reference number to the lua registry
// expects the context to be reference number to the lua registry
void Siwa_PlatformAuthenticateWithApple();
// Triggered by a call from lua to check if a provided apple id grants this app permission to use that id.
// expects the callback to be a reference number to the lua registry
// expects the context to be reference number to the lua registry
void Siwa_PlatformGetCredentialState();
#endif

184
siwa/src/siwa_ios.mm Executable file
View File

@@ -0,0 +1,184 @@
#if defined(DM_PLATFORM_IOS)
#include "siwa.h"
#include <AuthenticationServices/AuthenticationServices.h>
#include <dmsdk/sdk.h>
#include <stdlib.h>
#include <string.h>
// The sign in with Apple flow expects us to have a delegate to which it can both pass data from the sign in flow
// but also how to figure out in which UI context it should display the native login UI.
// This class is that delegate.
// It also owns the provider that is both used for the sign in flow, as well as for credential state checking.
API_AVAILABLE(ios(13.0))
@interface SiwaManager : NSObject <ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
@property (nonatomic, strong) ASAuthorizationAppleIDProvider *m_idProvider;
@end
@implementation SiwaManager
- (instancetype) init
{
self = [super init];
if (self)
{
self.m_idProvider = [[ASAuthorizationAppleIDProvider alloc] init];
}
return self;
}
// Check if the user id provided to use from lua still grants our app permission
// to use it for sign in. User's can revoke app's permissions to use the id for sign in
// at any time, so we need to be able to monitor this.
// The possible results are revoked(0), authorized(1), and unknown(2).
// In practice, we have recieved unknown when revoking permission to this test app
// so we should treat both revoked and unknown as unauthorized.
- (void) getCredentialState
{
char* userId = Siwa_GetUserId();
NSString* user_id_string = [[NSString alloc] initWithUTF8String:userId];
[self.m_idProvider getCredentialStateForUserID: user_id_string
completion: ^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError* error) {
// TODO: docs provide no information about what type of errors we can expect:
// https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider/3175423-getcredentialstateforuserid?language=objc
if (error) {
NSString *errorMessage = [NSString stringWithFormat: @"getCredentialStateForUserID completed with error: %@", [error localizedDescription]];
dmLogError([errorMessage UTF8String]);
}
SiwaCredentialState state = STATE_UNKNOWN;
switch(credentialState) {
case ASAuthorizationAppleIDProviderCredentialAuthorized:
dmLogInfo("credential state: ASAuthorizationAppleIDProviderCredentialAuthorized");
state = STATE_AUTHORIZED;
break;
case ASAuthorizationAppleIDProviderCredentialRevoked:
dmLogInfo("credential state: ASAuthorizationAppleIDProviderCredentialRevoked");
state = STATE_REVOKED;
break;
case ASAuthorizationAppleIDProviderCredentialNotFound:
dmLogInfo("credential state: ASAuthorizationAppleIDProviderCredentialNotFound");
state = STATE_NOT_FOUND;
break;
default:
dmLogInfo("credential state: unknown!!!");
break;
}
Siwa_QueueCredentialCallback(userId, state);
}];
}
// triggers the sign in with Apple native ui flow to begin.
- (void) loginWithUI
{
ASAuthorizationAppleIDRequest* request = [self.m_idProvider createRequest];
request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
ASAuthorizationController* authController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
authController.presentationContextProvider = self;
authController.delegate = self;
[authController performRequests];
}
// the Auth controller needs to specify where to display the native login.
// this is the function our SiwaManager delegate has to implement to provide that information.
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {
UIWindow *window = [UIApplication sharedApplication].keyWindow;
return window;
}
// the Auth controller callback for getting a response back from apple for a sign in
- (void)authorizationController:(ASAuthorizationController *)controller
didCompleteWithAuthorization:(ASAuthorization *)authorization {
if ([authorization.credential class] == [ASAuthorizationAppleIDCredential class]) {
ASAuthorizationAppleIDCredential* appleIdCredential = ((ASAuthorizationAppleIDCredential*) authorization.credential);
const char* appleUserId = [appleIdCredential.user UTF8String];
const char* email = [appleIdCredential.email UTF8String];
const char* givenName = [appleIdCredential.fullName.givenName UTF8String];
const char* familyName = [appleIdCredential.fullName.familyName UTF8String];
SiwaUserDetectionStatus userDetectionStatus = STATUS_UNSUPPORTED;
if (appleIdCredential.realUserStatus == ASUserDetectionStatusLikelyReal)
{
userDetectionStatus = STATUS_LIKELY_REAL;
}
else if (appleIdCredential.realUserStatus == ASUserDetectionStatusUnknown)
{
userDetectionStatus = STATUS_UNKNOWN;
}
appleIdCredential.realUserStatus;
NSString* tokenString = [[NSString alloc] initWithData:appleIdCredential.identityToken encoding:NSUTF8StringEncoding];
const char* identityToken = [tokenString UTF8String];
Siwa_QueueAuthSuccessCallback(identityToken, appleUserId, email, givenName, familyName, userDetectionStatus);
}
else
{
Siwa_QueueAuthFailureCallback("authorization failed!");
}
}
// The Auth controller callback for getting an error during authorization
- (void)authorizationController:(ASAuthorizationController *)controller
didCompleteWithError:(NSError *)error {
NSString *errorMessage = [NSString stringWithFormat: @"Authorization error: %@", [error localizedDescription]];
Siwa_QueueAuthFailureCallback([errorMessage UTF8String]);
}
@end
API_AVAILABLE(ios(13.0))
static SiwaManager* g_SiwaManager = nil;
API_AVAILABLE(ios(13.0))
SiwaManager* GetSiwaManager()
{
if(g_SiwaManager == nil)
{
g_SiwaManager = [[SiwaManager alloc] init];
}
return g_SiwaManager;
}
API_AVAILABLE(ios(13.0))
// Kicks off the request to get the credential state of a provided user id.
void Siwa_PlatformDoGetCredentialState() {
SiwaManager *siwaMan = GetSiwaManager();
[siwaMan getCredentialState];
return;
}
API_AVAILABLE(ios(13.0))
// Kicks off the sign in with apple flow.
void Siwa_PlatformDoAuthenticateWithApple() {
SiwaManager *siwaMan = GetSiwaManager();
[siwaMan loginWithUI];
}
// Checks if Siwa is supported on this device by seeing if the main
// class involved in all the siwa requests we use exists.
bool Siwa_PlatformIsSupported()
{
return ([ASAuthorizationAppleIDProvider class] != nil);
}
void Siwa_PlatformGetCredentialState() {
Siwa_PlatformDoGetCredentialState();
}
void Siwa_PlatformAuthenticateWithApple() {
Siwa_PlatformDoAuthenticateWithApple();
}
#endif

6
siwa/src/siwa_null.cpp Executable file
View File

@@ -0,0 +1,6 @@
#if !defined(DM_PLATFORM_IOS)
extern "C" void siwa()
{
}
#endif