diff --git a/OneTimeKit/Models/OTPBase.h b/OneTimeKit/Models/OTPBase.h index cfd0f80..f14ece8 100644 --- a/OneTimeKit/Models/OTPBase.h +++ b/OneTimeKit/Models/OTPBase.h @@ -62,6 +62,8 @@ typedef NS_ENUM(NSInteger, OTPropertiesVersion) { // subclasses must provide a default `factor` implementation - (uint64_t)factor; - (NSString *)password; +- (NSString *)secret; +- (NSString *)qrString:(NSArray *)input; - (NSArray *)queryItems; diff --git a/OneTimeKit/Models/OTPBase.m b/OneTimeKit/Models/OTPBase.m index 43dd756..ccf9b8a 100644 --- a/OneTimeKit/Models/OTPBase.m +++ b/OneTimeKit/Models/OTPBase.m @@ -174,6 +174,7 @@ - (NSString *)passwordForFactor:(uint64_t)factor { for (char *valuePtr = value + digits; value < valuePtr; head /= base) { *(--valuePtr) = (head % base) + '0'; } + //NSString* newStr = [[NSString alloc] initWithData:self.algorithm encoding:NSUTF8StringEncoding]; return [[NSString alloc] initWithBytesNoCopy:value length:digits encoding:NSASCIIStringEncoding freeWhenDone:YES]; } @@ -186,6 +187,14 @@ - (NSString *)password { return [self passwordForFactor:[self factor]]; } +- (NSString *)secret { + return [self.key base32EncodedStringWithOptions:NSDataBase32EncodingOptionsNoPad]; +} + +- (NSString *)qrString:(NSArray *)input { + return [NSString stringWithFormat: @"otpauth://totp/%@:%@?secret=%@&issuer=%@", input[0],input[1],self.secret,input[0]]; +} + - (NSDictionary *)properties { return @{ OTPDigitPropertyKey : @(self.digits), diff --git a/OneTimeKit/Services/OTQRCreatorService.h b/OneTimeKit/Services/OTQRCreatorService.h new file mode 100644 index 0000000..f505657 --- /dev/null +++ b/OneTimeKit/Services/OTQRCreatorService.h @@ -0,0 +1,23 @@ +// +// OTQRCreatorService.h +// onetime +// +// Created by owl on 14.09.24. +// Copyright © 2024 Leptos. All rights reserved. +// +#import +#import + +@interface OTQRCreatorService : NSObject + + ++ (instancetype)shared; +// Declaration of a class method `generateQRCodeFromString:` which generates a QR code from a given string. +// Parameters: +// - string: The string to be encoded into a QR code. +// Returns: An UIImage object representing the generated QR code. ++ (UIImage *)generateQRCodeFromString:(NSString *)string; ++ (NSURL *)generateQRCodeFileFromString:(NSString *)string; + + +@end diff --git a/OneTimeKit/Services/OTQRCreatorService.m b/OneTimeKit/Services/OTQRCreatorService.m new file mode 100644 index 0000000..47f1927 --- /dev/null +++ b/OneTimeKit/Services/OTQRCreatorService.m @@ -0,0 +1,63 @@ +#import +#import +#import + +@interface OTQRCreatorService : NSObject + ++ (UIImage *)generateQRCodeFromString:(NSString *)string; + +@end + +@implementation OTQRCreatorService + ++ (CIImage *)generateQRCodeImageFromString:(NSString *)string { + // Create a CIFilter object for generating QR codes. + CIFilter *qrCodeFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"]; + + // Set the input data for the QR code filter from input string, converted to UTF-8 + [qrCodeFilter setValue:[string dataUsingEncoding:NSUTF8StringEncoding] forKey:@"inputMessage"]; + + // Get the output image from the QR code filter. + CIImage *outputImage = qrCodeFilter.outputImage; + + return outputImage; +} + ++ (UIImage *)generateQRCodeFromString:(NSString *)string { + /*CIImage *outputImage = [self generateQRCodeImageFromString:string]; + + CIContext *context = [CIContext contextWithOptions:nil]; + + // Create a CGImageRef from the CIImage using the CIContext. + CGImageRef cgImage = [context createCGImage:outputImage fromRect:outputImage.extent]; + + // Create a UIImage from the CGImageRef. + UIImage *qrCodeImage = [UIImage imageWithCGImage:cgImage]; + + // Release the CGImageRef. + CGImageRelease(cgImage);*/ + + return [[UIImage alloc] initWithCIImage:[self generateQRCodeImageFromString:string]];; +} + ++ (NSURL *)generateQRCodeFileFromString: (NSString *)string { + + UIImage *uiImage = [[UIImage alloc] initWithCIImage:[self generateQRCodeImageFromString:string]]; + + NSData *NSuiImage = UIImagePNGRepresentation(uiImage); + //NSData *imageData = UIImagePNGRepresentation(uiImage); + + // Create a temporary file path + NSString *tempDir = NSTemporaryDirectory(); + NSString *tempFile = [tempDir stringByAppendingPathComponent:@"OTP.png"]; + + // Write the PNG data to the temporary file + [NSuiImage writeToFile:tempFile atomically:YES]; + + // Create a file URL for the temporary file + NSURL *fileURL = [NSURL fileURLWithPath:tempFile]; + + return fileURL; +} + +@end diff --git a/onetime/Views/OTPassTableViewCell.m b/onetime/Views/OTPassTableViewCell.m index afe1cdd..e507a01 100644 --- a/onetime/Views/OTPassTableViewCell.m +++ b/onetime/Views/OTPassTableViewCell.m @@ -11,6 +11,7 @@ #import "../../OneTimeKit/Models/OTPTime.h" #import "../../OneTimeKit/Models/OTPHash.h" #import "../../OneTimeKit/Services/OTBagCenter.h" +#import "../../OneTimeKit/Services/OTQRCreatorService.h" @implementation OTPassTableViewCell { uint64_t _lastFactor; @@ -236,6 +237,25 @@ - (void)delete:(id)sender { [self.actionDelegate promptDeleteBag:self.bag]; } +- (void)extract:(id)sender { + UIPasteboard.generalPasteboard.string = self.bag.generator.secret; +} + +- (void)createQR:(id)sender { + NSArray *inputParameter = @[self.bag.issuer,self.bag.account]; // The parameter you want to pass + NSString *inputString = [self.bag.generator qrString:inputParameter]; + NSURL *qrCodeImage_URL = [OTQRCreatorService generateQRCodeFileFromString:inputString]; + // Copy the file URL to the pasteboard + [UIPasteboard generalPasteboard].items = @[@{(NSString *)kUTTypeFileURL : qrCodeImage_URL}]; +} + +- (void)copyQR:(id)sender { + NSArray *inputParameter = @[self.bag.issuer,self.bag.account]; // The parameter you want to pass + NSString *inputString = [self.bag.generator qrString:inputParameter]; + UIImage *qrCodeImage = [OTQRCreatorService generateQRCodeFromString:inputString]; + // Copy the image data to the pasteboard + UIPasteboard.generalPasteboard.image = qrCodeImage; +} // MARK: - UIContextMenuInteractionDelegate - (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location API_AVAILABLE(ios(13.0)) { @@ -256,6 +276,21 @@ - (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction input:@"\b" modifierFlags:0 propertyList:nil]; deleteCommand.attributes = UIMenuElementAttributesDestructive; + UIKeyCommand *extractCommand = [UIKeyCommand commandWithTitle:@"Extract Token to Clipboard" + image:[UIImage systemImageNamed:@"doc.on.clipboard"] + action:@selector(extract:) + input:@"x" modifierFlags:0 + propertyList:nil]; + UIKeyCommand *createQRCommand = [UIKeyCommand commandWithTitle:@"Copy Token QR Code File" + image:[UIImage systemImageNamed:@"doc.on.clipboard"] + action:@selector(createQR:) + input:@"x" modifierFlags:0 + propertyList:nil]; + UIKeyCommand *copyQRCommand = [UIKeyCommand commandWithTitle:@"Copy Token QR Code" + image:[UIImage systemImageNamed:@"doc.on.clipboard"] + action:@selector(copyQR:) + input:@"x" modifierFlags:0 + propertyList:nil]; NSMutableArray *additionalActions = [NSMutableArray array]; @@ -272,7 +307,7 @@ - (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction NSLog(@"openURLCompletedSuccessfully: %@", success ? @"YES" : @"NO"); }]; }]; - [additionalActions addObject:exportAction]; + [additionalActions addObjectsFromArray:@[exportAction,extractCommand,copyQRCommand,createQRCommand]]; } return [UIContextMenuConfiguration configurationWithIdentifier:nil previewProvider:nil @@ -280,7 +315,7 @@ - (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction return [UIMenu menuWithTitle:@"" children:@[ [UIMenu menuWithTitle:@"" image:nil identifier:nil options:UIMenuOptionsDisplayInline children:@[ copyCommand, - deleteCommand + deleteCommand, ]], [UIMenu menuWithTitle:@"" image:nil identifier:nil options:UIMenuOptionsDisplayInline children:additionalActions] ]];