Line data Source code
1 : import 'dart:convert';
2 : import 'package:jose/jose.dart';
3 : import 'package:stream_feed_dart/src/core/http/token.dart';
4 : import 'package:stream_feed_dart/src/core/models/feed_id.dart';
5 :
6 : /// Actions permissions
7 14 : enum TokenAction {
8 : /// allows any operations
9 : any,
10 :
11 : /// allows read operations
12 : read,
13 :
14 : /// allows write operations
15 : write,
16 :
17 : /// allows delete operations
18 : delete,
19 : }
20 :
21 : extension TokenActionX on TokenAction {
22 6 : String? get action => {
23 : TokenAction.any: '*',
24 : TokenAction.read: 'read',
25 : TokenAction.write: 'write',
26 : TokenAction.delete: 'delete',
27 3 : }[this];
28 : }
29 :
30 : /// Resource Access Restrictions
31 14 : enum TokenResource {
32 : /// allows access to any resource
33 : any,
34 :
35 : /// allows access to [Activity] resource
36 : activities,
37 :
38 : /// allows access to analytics resource
39 : analytics,
40 :
41 : /// allows access to analyticsRedirect resource
42 : analyticsRedirect,
43 :
44 : /// allows access to [CollectionEntry] resource
45 : collections,
46 :
47 : /// allows access to files resource
48 : files,
49 :
50 : /// allows access to [Feed] resource
51 : feed,
52 :
53 : /// allows access to feedTargets resource
54 : feedTargets,
55 :
56 : /// allows access to [Follow] resource
57 : follower,
58 :
59 : /// allows access to [OpenGraph] resource
60 : openGraph,
61 :
62 : /// token resource that allows access to personalization resource
63 : personalization,
64 :
65 : /// token resource that allows access to [Reaction] resource
66 : reactions,
67 :
68 : /// token resource that allows access to [User] resource
69 : users,
70 : }
71 :
72 : /// Convenient class Extension to on [TokenResource] enum
73 : extension TokenResourceX on TokenResource {
74 : /// Convenient method Extension to stringify the [TokenResource] enum
75 6 : String? get resource => {
76 : TokenResource.any: '*',
77 : TokenResource.activities: 'activities',
78 : TokenResource.analytics: 'analytics',
79 : TokenResource.analyticsRedirect: 'redirect_and_track',
80 : TokenResource.collections: 'collections',
81 : TokenResource.files: 'files',
82 : TokenResource.feed: 'feed',
83 : TokenResource.feedTargets: 'feed_targets',
84 : TokenResource.follower: 'follower',
85 : TokenResource.openGraph: 'url',
86 : TokenResource.personalization: 'ppersonalization',
87 : TokenResource.reactions: 'reactions',
88 : TokenResource.users: 'users',
89 3 : }[this];
90 : }
91 :
92 : class TokenHelper {
93 0 : const TokenHelper();
94 :
95 1 : static Token buildFeedToken(
96 : String secret,
97 : TokenAction action, [
98 : FeedId? feed,
99 : ]) =>
100 1 : _buildBackendToken(
101 0 : secret, TokenResource.feed, action, feed?.claim ?? '*');
102 :
103 1 : static Token buildFollowToken(
104 : String secret,
105 : TokenAction action, [
106 : FeedId? feed,
107 : ]) =>
108 1 : _buildBackendToken(
109 0 : secret, TokenResource.follower, action, feed?.claim ?? '*');
110 :
111 1 : static Token buildReactionToken(String secret, TokenAction action) =>
112 1 : _buildBackendToken(secret, TokenResource.reactions, action, '*');
113 :
114 1 : static Token buildActivityToken(String secret, TokenAction action) =>
115 1 : _buildBackendToken(secret, TokenResource.activities, action, '*');
116 :
117 1 : static Token buildUsersToken(String secret, TokenAction action) =>
118 1 : _buildBackendToken(secret, TokenResource.users, action, '*');
119 :
120 2 : static Token buildCollectionsToken(String secret, TokenAction action) =>
121 2 : _buildBackendToken(secret, TokenResource.collections, action, '*');
122 :
123 4 : static Token buildOpenGraphToken(String secret) => _buildBackendToken(
124 : secret, TokenResource.openGraph, TokenAction.read, '*');
125 :
126 1 : static Token buildToTargetUpdateToken(
127 : String secret,
128 : TokenAction action, [
129 : FeedId? feed,
130 : ]) =>
131 1 : _buildBackendToken(
132 0 : secret, TokenResource.feedTargets, action, feed?.claim ?? '*');
133 :
134 1 : static Token buildFilesToken(String secret, TokenAction action) =>
135 1 : _buildBackendToken(secret, TokenResource.files, action, '*');
136 :
137 2 : static Token buildFrontendToken(
138 : String secret,
139 : String userId, {
140 : DateTime? expiresAt,
141 : }) {
142 2 : final claims = <String, Object>{
143 : 'user_id': userId,
144 : };
145 :
146 2 : return Token(
147 2 : issueJwtHS256(secret: secret, expiresAt: expiresAt, claims: claims));
148 : }
149 :
150 : /// Creates the JWT token for [feedId], [resource] and [action]
151 : /// using the api [secret]
152 3 : static Token _buildBackendToken(
153 : String secret,
154 : TokenResource resource,
155 : TokenAction action,
156 : String feedId, {
157 : String? userId,
158 : }) {
159 3 : final claims = <String, Object?>{
160 3 : 'resource': resource.resource,
161 3 : 'action': action.action,
162 : 'feed_id': feedId,
163 : };
164 : if (userId != null) {
165 0 : claims['user_id'] = userId;
166 : }
167 :
168 6 : return Token(issueJwtHS256(secret: secret, claims: claims));
169 : }
170 : }
171 :
172 3 : String issueJwtHS256({
173 : required String secret,
174 : required Map<String, Object?>? claims,
175 : DateTime? expiresAt,
176 : }) {
177 6 : final claimSet = JsonWebTokenClaims.fromJson({
178 6 : 'exp': DateTime.now()
179 3 : .add(const Duration(seconds: 1200))
180 6 : .millisecondsSinceEpoch ~/
181 : 1000,
182 15 : 'iat': DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000,
183 3 : if (claims != null) ...claims,
184 : });
185 :
186 : // create a builder, decoding the JWT in a JWS, so using a
187 : // JsonWebSignatureBuilder
188 3 : final builder = JsonWebSignatureBuilder()
189 :
190 : // set the content
191 6 : ..jsonContent = claimSet.toJson()
192 :
193 : // add a key to sign, can only add one for JWT
194 3 : ..addRecipient(
195 6 : JsonWebKey.fromJson({
196 : 'kty': 'oct',
197 3 : 'k': base64Urlencode(secret),
198 : }),
199 : algorithm: 'HS256')
200 : // builder.recipients
201 3 : ..setProtectedHeader('typ', 'JWT');
202 : // build the jws
203 3 : final jws = builder.build();
204 :
205 : // output the compact serialization
206 3 : return jws.toCompactSerialization();
207 : }
208 :
209 3 : String base64Urlencode(String secret) {
210 3 : final Codec<String?, String> stringToBase64Url = utf8.fuse(base64Url);
211 3 : final encoded = stringToBase64Url.encode(secret);
212 : return encoded;
213 : }
|