Add API token commands
This commit is contained in:
		
							
								
								
									
										66
									
								
								PluralKit.Bot/Commands/APICommands.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								PluralKit.Bot/Commands/APICommands.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| using System.Threading.Tasks; | ||||
| using Discord; | ||||
| using Discord.Commands; | ||||
|  | ||||
| namespace PluralKit.Bot.Commands | ||||
| { | ||||
|     [Group("token")] | ||||
|     public class APICommands: ModuleBase<PKCommandContext> | ||||
|     { | ||||
|         public SystemStore Systems { get; set; } | ||||
|          | ||||
|         [Command] | ||||
|         [MustHaveSystem] | ||||
|         [Remarks("token")] | ||||
|         public async Task GetToken() | ||||
|         { | ||||
|             // Get or make a token | ||||
|             var token = Context.SenderSystem.Token ?? await MakeAndSetNewToken(); | ||||
|              | ||||
|             // If we're not already in a DM, reply with a reminder to check | ||||
|             if (!(Context.Channel is IDMChannel)) | ||||
|             { | ||||
|                 await Context.Channel.SendMessageAsync($"{Emojis.Success} Check your DMs!"); | ||||
|             } | ||||
|  | ||||
|             // DM the user a security disclaimer, and then the token in a separate message (for easy copying on mobile) | ||||
|             await Context.User.SendMessageAsync($"{Emojis.Warn} Please note that this grants access to modify (and delete!) all your system data, so keep it safe and secure. If it leaks or you need a new one, you can invalidate this one with `pk;token refresh`.\n\nYour token is below:"); | ||||
|             await Context.User.SendMessageAsync(token); | ||||
|         } | ||||
|  | ||||
|         private async Task<string> MakeAndSetNewToken() | ||||
|         { | ||||
|             Context.SenderSystem.Token = PluralKit.Utils.GenerateToken(); | ||||
|             await Systems.Save(Context.SenderSystem); | ||||
|             return Context.SenderSystem.Token; | ||||
|         } | ||||
|  | ||||
|         [Command("refresh")] | ||||
|         [MustHaveSystem] | ||||
|         [Alias("expire", "invalidate", "update", "new")] | ||||
|         [Remarks("token refresh")] | ||||
|         public async Task RefreshToken() | ||||
|         { | ||||
|             if (Context.SenderSystem.Token == null) | ||||
|             { | ||||
|                 // If we don't have a token, call the other method instead | ||||
|                 // This does pretty much the same thing, except words the messages more appropriately for that :) | ||||
|                 await GetToken(); | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             // Make a new token from scratch | ||||
|             var token = await MakeAndSetNewToken(); | ||||
|              | ||||
|             // If we're not already in a DM, reply with a reminder to check | ||||
|             if (!(Context.Channel is IDMChannel)) | ||||
|             { | ||||
|                 await Context.Channel.SendMessageAsync($"{Emojis.Success} Check your DMs!"); | ||||
|             } | ||||
|              | ||||
|             // DM the user an invalidation disclaimer, and then the token in a separate message (for easy copying on mobile) | ||||
|             await Context.User.SendMessageAsync($"{Emojis.Warn} Your previous API token has been invalidated. You will need to change it anywhere it's currently used.\n\nYour token is below:"); | ||||
|             await Context.User.SendMessageAsync(token); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,8 +1,6 @@ | ||||
| using System; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using System.Globalization; | ||||
| using System.Linq; | ||||
| using System.Security.Cryptography; | ||||
| using System.Text.RegularExpressions; | ||||
| using NodaTime; | ||||
| using NodaTime.Text; | ||||
| @@ -24,6 +22,13 @@ namespace PluralKit | ||||
|             return hid; | ||||
|         } | ||||
|  | ||||
|         public static string GenerateToken() | ||||
|         { | ||||
|             var buf = new byte[48]; // Results in a 64-byte Base64 string (no padding) | ||||
|             new RNGCryptoServiceProvider().GetBytes(buf); | ||||
|             return Convert.ToBase64String(buf); | ||||
|         } | ||||
|  | ||||
|         public static string Truncate(this string str, int maxLength, string ellipsis = "...") { | ||||
|             if (str.Length < maxLength) return str; | ||||
|             return str.Substring(0, maxLength - ellipsis.Length) + ellipsis; | ||||
| @@ -225,21 +230,19 @@ namespace PluralKit | ||||
|     { | ||||
|         public static IPattern<Instant> TimestampExportFormat = InstantPattern.CreateWithInvariantCulture("g"); | ||||
|         public static IPattern<LocalDate> DateExportFormat = LocalDatePattern.CreateWithInvariantCulture("yyyy-MM-dd"); | ||||
|         public static IPattern<Duration> DurationFormat; | ||||
|          | ||||
|         // We create a composite pattern that only shows the two most significant things | ||||
|         // eg. if we have something with nonzero day component, we show <x>d <x>h, but if it's | ||||
|         // a smaller duration we may only bother with showing <x>h <x>m or <x>m <x>s | ||||
|         public static IPattern<Duration> DurationFormat = new CompositePatternBuilder<Duration> | ||||
|         { | ||||
|             {DurationPattern.CreateWithInvariantCulture("D'd' h'h'"), d => d.Days > 0}, | ||||
|             {DurationPattern.CreateWithInvariantCulture("H'h' m'm'"), d => d.Hours > 0}, | ||||
|             {DurationPattern.CreateWithInvariantCulture("m'm' s's'"), d => d.Minutes > 0}, | ||||
|             {DurationPattern.CreateWithInvariantCulture("s's'"), d => true} | ||||
|         }.Build(); | ||||
|          | ||||
|         public static IPattern<LocalDateTime> LocalDateTimeFormat = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss"); | ||||
|         public static IPattern<ZonedDateTime> ZonedDateTimeFormat = ZonedDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss x", DateTimeZoneProviders.Tzdb); | ||||
|  | ||||
|         static Formats() | ||||
|         { | ||||
|             // We create a composite pattern that only shows the two most significant things | ||||
|             // eg. if we have something with nonzero day component, we show <x>d <x>h, but if it's | ||||
|             // a smaller duration we may only bother with showing <x>h <x>m or <x>m <x>s | ||||
|             var compositeDuration = new CompositePatternBuilder<Duration>(); | ||||
|             compositeDuration.Add(DurationPattern.CreateWithInvariantCulture("D'd' h'h'"), d => d.Days > 0); | ||||
|             compositeDuration.Add(DurationPattern.CreateWithInvariantCulture("H'h' m'm'"), d => d.Hours > 0); | ||||
|             compositeDuration.Add(DurationPattern.CreateWithInvariantCulture("m'm' s's'"), d => d.Minutes > 0); | ||||
|             compositeDuration.Add(DurationPattern.CreateWithInvariantCulture("s's'"), d => true); | ||||
|             DurationFormat = compositeDuration.Build(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user