···199 unique(starred_by_did, repo_at)
200 );
2010000000000202 create table if not exists emails (
203 id integer primary key autoincrement,
204 did text not null,
···409 foreign key (pipeline_knot, pipeline_rkey)
410 references pipelines (knot, rkey)
411 on delete cascade
0000000000000000412 );
413414 create table if not exists migrations (
···199 unique(starred_by_did, repo_at)
200 );
201202+ create table if not exists reactions (
203+ id integer primary key autoincrement,
204+ reacted_by_did text not null,
205+ thread_at text not null,
206+ kind text not null,
207+ rkey text not null,
208+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
209+ unique(reacted_by_did, thread_at, kind)
210+ );
211+212 create table if not exists emails (
213 id integer primary key autoincrement,
214 did text not null,
···419 foreign key (pipeline_knot, pipeline_rkey)
420 references pipelines (knot, rkey)
421 on delete cascade
422+ );
423+424+ create table if not exists repo_languages (
425+ -- identifiers
426+ id integer primary key autoincrement,
427+428+ -- repo identifiers
429+ repo_at text not null,
430+ ref text not null,
431+ is_default_ref integer not null default 0,
432+433+ -- language breakdown
434+ language text not null,
435+ bytes integer not null check (bytes >= 0),
436+437+ unique(repo_at, ref, language)
438 );
439440 create table if not exists migrations (
+2-2
appview/db/issues.go
···277}
278279func GetIssueWithComments(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, []Comment, error) {
280- query := `select owner_did, issue_id, created, title, body, open from issues where repo_at = ? and issue_id = ?`
281 row := e.QueryRow(query, repoAt, issueId)
282283 var issue Issue
284 var createdAt string
285- err := row.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open)
286 if err != nil {
287 return nil, nil, err
288 }
···23import (
4 "context"
5- "crypto/hmac"
6- "crypto/sha256"
7- "encoding/hex"
8 "fmt"
9 "log"
10 "log/slog"
···202}
203204// requires auth
205-func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) {
206- switch r.Method {
207- case http.MethodGet:
208- // list open registrations under this did
209-210- return
211- case http.MethodPost:
212- session, err := s.oauth.Stores().Get(r, oauth.SessionName)
213- if err != nil || session.IsNew {
214- log.Println("unauthorized attempt to generate registration key")
215- http.Error(w, "Forbidden", http.StatusUnauthorized)
216- return
217- }
218-219- did := session.Values[oauth.SessionDid].(string)
220-221- // check if domain is valid url, and strip extra bits down to just host
222- domain := r.FormValue("domain")
223- if domain == "" {
224- http.Error(w, "Invalid form", http.StatusBadRequest)
225- return
226- }
227-228- key, err := db.GenerateRegistrationKey(s.db, domain, did)
229-230- if err != nil {
231- log.Println(err)
232- http.Error(w, "unable to register this domain", http.StatusNotAcceptable)
233- return
234- }
235-236- w.Write([]byte(key))
237- }
238-}
239240func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
241 user := chi.URLParam(r, "user")
···270}
271272// create a signed request and check if a node responds to that
273-func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) {
274- user := s.oauth.GetUser(r)
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000275276- domain := chi.URLParam(r, "domain")
277- if domain == "" {
278- http.Error(w, "malformed url", http.StatusBadRequest)
279- return
280- }
281- log.Println("checking ", domain)
282-283- secret, err := db.GetRegistrationKey(s.db, domain)
284- if err != nil {
285- log.Printf("no key found for domain %s: %s\n", domain, err)
286- return
287- }
288-289- client, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev)
290- if err != nil {
291- log.Println("failed to create client to ", domain)
292- }
293-294- resp, err := client.Init(user.Did)
295- if err != nil {
296- w.Write([]byte("no dice"))
297- log.Println("domain was unreachable after 5 seconds")
298- return
299- }
300-301- if resp.StatusCode == http.StatusConflict {
302- log.Println("status conflict", resp.StatusCode)
303- w.Write([]byte("already registered, sorry!"))
304- return
305- }
306-307- if resp.StatusCode != http.StatusNoContent {
308- log.Println("status nok", resp.StatusCode)
309- w.Write([]byte("no dice"))
310- return
311- }
312-313- // verify response mac
314- signature := resp.Header.Get("X-Signature")
315- signatureBytes, err := hex.DecodeString(signature)
316- if err != nil {
317- return
318- }
319-320- expectedMac := hmac.New(sha256.New, []byte(secret))
321- expectedMac.Write([]byte("ok"))
322-323- if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) {
324- log.Printf("response body signature mismatch: %x\n", signatureBytes)
325- return
326- }
327-328- tx, err := s.db.BeginTx(r.Context(), nil)
329- if err != nil {
330- log.Println("failed to start tx", err)
331- http.Error(w, err.Error(), http.StatusInternalServerError)
332- return
333- }
334- defer func() {
335- tx.Rollback()
336- err = s.enforcer.E.LoadPolicy()
337- if err != nil {
338- log.Println("failed to rollback policies")
339- }
340- }()
341-342- // mark as registered
343- err = db.Register(tx, domain)
344- if err != nil {
345- log.Println("failed to register domain", err)
346- http.Error(w, err.Error(), http.StatusInternalServerError)
347- return
348- }
349-350- // set permissions for this did as owner
351- reg, err := db.RegistrationByDomain(tx, domain)
352- if err != nil {
353- log.Println("failed to register domain", err)
354- http.Error(w, err.Error(), http.StatusInternalServerError)
355- return
356- }
357-358- // add basic acls for this domain
359- err = s.enforcer.AddKnot(domain)
360- if err != nil {
361- log.Println("failed to setup owner of domain", err)
362- http.Error(w, err.Error(), http.StatusInternalServerError)
363- return
364- }
365-366- // add this did as owner of this domain
367- err = s.enforcer.AddKnotOwner(domain, reg.ByDid)
368- if err != nil {
369- log.Println("failed to setup owner of domain", err)
370- http.Error(w, err.Error(), http.StatusInternalServerError)
371- return
372- }
373-374- err = tx.Commit()
375- if err != nil {
376- log.Println("failed to commit changes", err)
377- http.Error(w, err.Error(), http.StatusInternalServerError)
378- return
379- }
380-381- err = s.enforcer.E.SavePolicy()
382- if err != nil {
383- log.Println("failed to update ACLs", err)
384- http.Error(w, err.Error(), http.StatusInternalServerError)
385- return
386- }
387-388- // add this knot to knotstream
389- go s.knotstream.AddSource(
390- context.Background(),
391- eventconsumer.NewKnotSource(domain),
392- )
393-394- w.Write([]byte("check success"))
395-}
396-397-func (s *State) KnotServerInfo(w http.ResponseWriter, r *http.Request) {
398- domain := chi.URLParam(r, "domain")
399- if domain == "" {
400- http.Error(w, "malformed url", http.StatusBadRequest)
401- return
402- }
403-404- user := s.oauth.GetUser(r)
405- reg, err := db.RegistrationByDomain(s.db, domain)
406- if err != nil {
407- w.Write([]byte("failed to pull up registration info"))
408- return
409- }
410-411- var members []string
412- if reg.Registered != nil {
413- members, err = s.enforcer.GetUserByRole("server:member", domain)
414- if err != nil {
415- w.Write([]byte("failed to fetch member list"))
416- return
417- }
418- }
419-420- var didsToResolve []string
421- for _, m := range members {
422- didsToResolve = append(didsToResolve, m)
423- }
424- didsToResolve = append(didsToResolve, reg.ByDid)
425- resolvedIds := s.idResolver.ResolveIdents(r.Context(), didsToResolve)
426- didHandleMap := make(map[string]string)
427- for _, identity := range resolvedIds {
428- if !identity.Handle.IsInvalidHandle() {
429- didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
430- } else {
431- didHandleMap[identity.DID.String()] = identity.DID.String()
432- }
433- }
434-435- ok, err := s.enforcer.IsKnotOwner(user.Did, domain)
436- isOwner := err == nil && ok
437-438- p := pages.KnotParams{
439- LoggedInUser: user,
440- DidHandleMap: didHandleMap,
441- Registration: reg,
442- Members: members,
443- IsOwner: isOwner,
444- }
445-446- s.pages.Knot(w, p)
447-}
448449// get knots registered by this user
450-func (s *State) Knots(w http.ResponseWriter, r *http.Request) {
451- // for now, this is just pubkeys
452- user := s.oauth.GetUser(r)
453- registrations, err := db.RegistrationsByDid(s.db, user.Did)
454- if err != nil {
455- log.Println(err)
456- }
457-458- s.pages.Knots(w, pages.KnotsParams{
459- LoggedInUser: user,
460- Registrations: registrations,
461- })
462-}
463464// list members of domain, requires auth and requires owner status
465-func (s *State) ListMembers(w http.ResponseWriter, r *http.Request) {
466- domain := chi.URLParam(r, "domain")
467- if domain == "" {
468- http.Error(w, "malformed url", http.StatusBadRequest)
469- return
470- }
471-472- // list all members for this domain
473- memberDids, err := s.enforcer.GetUserByRole("server:member", domain)
474- if err != nil {
475- w.Write([]byte("failed to fetch member list"))
476- return
477- }
478-479- w.Write([]byte(strings.Join(memberDids, "\n")))
480- return
481-}
482483// add member to domain, requires auth and requires invite access
484-func (s *State) AddMember(w http.ResponseWriter, r *http.Request) {
485- domain := chi.URLParam(r, "domain")
486- if domain == "" {
487- http.Error(w, "malformed url", http.StatusBadRequest)
488- return
489- }
000000000000000000000000000000000000000000000000000000000000000000000000490491- subjectIdentifier := r.FormValue("subject")
492- if subjectIdentifier == "" {
493- http.Error(w, "malformed form", http.StatusBadRequest)
494- return
495- }
496-497- subjectIdentity, err := s.idResolver.ResolveIdent(r.Context(), subjectIdentifier)
498- if err != nil {
499- w.Write([]byte("failed to resolve member did to a handle"))
500- return
501- }
502- log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain)
503-504- // announce this relation into the firehose, store into owners' pds
505- client, err := s.oauth.AuthorizedClient(r)
506- if err != nil {
507- http.Error(w, "failed to authorize client", http.StatusInternalServerError)
508- return
509- }
510- currentUser := s.oauth.GetUser(r)
511- createdAt := time.Now().Format(time.RFC3339)
512- resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
513- Collection: tangled.KnotMemberNSID,
514- Repo: currentUser.Did,
515- Rkey: appview.TID(),
516- Record: &lexutil.LexiconTypeDecoder{
517- Val: &tangled.KnotMember{
518- Subject: subjectIdentity.DID.String(),
519- Domain: domain,
520- CreatedAt: createdAt,
521- }},
522- })
523-524- // invalid record
525- if err != nil {
526- log.Printf("failed to create record: %s", err)
527- return
528- }
529- log.Println("created atproto record: ", resp.Uri)
530-531- secret, err := db.GetRegistrationKey(s.db, domain)
532- if err != nil {
533- log.Printf("no key found for domain %s: %s\n", domain, err)
534- return
535- }
536-537- ksClient, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev)
538- if err != nil {
539- log.Println("failed to create client to ", domain)
540- return
541- }
542-543- ksResp, err := ksClient.AddMember(subjectIdentity.DID.String())
544- if err != nil {
545- log.Printf("failed to make request to %s: %s", domain, err)
546- return
547- }
548-549- if ksResp.StatusCode != http.StatusNoContent {
550- w.Write([]byte(fmt.Sprint("knotserver failed to add member: ", err)))
551- return
552- }
553-554- err = s.enforcer.AddKnotMember(domain, subjectIdentity.DID.String())
555- if err != nil {
556- w.Write([]byte(fmt.Sprint("failed to add member: ", err)))
557- return
558- }
559-560- w.Write([]byte(fmt.Sprint("added member: ", subjectIdentity.Handle.String())))
561-}
562-563-func (s *State) RemoveMember(w http.ResponseWriter, r *http.Request) {
564-}
565566func validateRepoName(name string) error {
567 // check for path traversal attempts
···23import (
4 "context"
0005 "fmt"
6 "log"
7 "log/slog"
···199}
200201// requires auth
202+// func (s *State) RegistrationKey(w http.ResponseWriter, r *http.Request) {
203+// switch r.Method {
204+// case http.MethodGet:
205+// // list open registrations under this did
206+//
207+// return
208+// case http.MethodPost:
209+// session, err := s.oauth.Stores().Get(r, oauth.SessionName)
210+// if err != nil || session.IsNew {
211+// log.Println("unauthorized attempt to generate registration key")
212+// http.Error(w, "Forbidden", http.StatusUnauthorized)
213+// return
214+// }
215+//
216+// did := session.Values[oauth.SessionDid].(string)
217+//
218+// // check if domain is valid url, and strip extra bits down to just host
219+// domain := r.FormValue("domain")
220+// if domain == "" {
221+// http.Error(w, "Invalid form", http.StatusBadRequest)
222+// return
223+// }
224+//
225+// key, err := db.GenerateRegistrationKey(s.db, domain, did)
226+//
227+// if err != nil {
228+// log.Println(err)
229+// http.Error(w, "unable to register this domain", http.StatusNotAcceptable)
230+// return
231+// }
232+//
233+// w.Write([]byte(key))
234+// }
235+// }
236237func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
238 user := chi.URLParam(r, "user")
···267}
268269// create a signed request and check if a node responds to that
270+// func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) {
271+// user := s.oauth.GetUser(r)
272+//
273+// noticeId := "operation-error"
274+// defaultErr := "Failed to register spindle. Try again later."
275+// fail := func() {
276+// s.pages.Notice(w, noticeId, defaultErr)
277+// }
278+//
279+// domain := chi.URLParam(r, "domain")
280+// if domain == "" {
281+// http.Error(w, "malformed url", http.StatusBadRequest)
282+// return
283+// }
284+// log.Println("checking ", domain)
285+//
286+// secret, err := db.GetRegistrationKey(s.db, domain)
287+// if err != nil {
288+// log.Printf("no key found for domain %s: %s\n", domain, err)
289+// return
290+// }
291+//
292+// client, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev)
293+// if err != nil {
294+// log.Println("failed to create client to ", domain)
295+// }
296+//
297+// resp, err := client.Init(user.Did)
298+// if err != nil {
299+// w.Write([]byte("no dice"))
300+// log.Println("domain was unreachable after 5 seconds")
301+// return
302+// }
303+//
304+// if resp.StatusCode == http.StatusConflict {
305+// log.Println("status conflict", resp.StatusCode)
306+// w.Write([]byte("already registered, sorry!"))
307+// return
308+// }
309+//
310+// if resp.StatusCode != http.StatusNoContent {
311+// log.Println("status nok", resp.StatusCode)
312+// w.Write([]byte("no dice"))
313+// return
314+// }
315+//
316+// // verify response mac
317+// signature := resp.Header.Get("X-Signature")
318+// signatureBytes, err := hex.DecodeString(signature)
319+// if err != nil {
320+// return
321+// }
322+//
323+// expectedMac := hmac.New(sha256.New, []byte(secret))
324+// expectedMac.Write([]byte("ok"))
325+//
326+// if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) {
327+// log.Printf("response body signature mismatch: %x\n", signatureBytes)
328+// return
329+// }
330+//
331+// tx, err := s.db.BeginTx(r.Context(), nil)
332+// if err != nil {
333+// log.Println("failed to start tx", err)
334+// http.Error(w, err.Error(), http.StatusInternalServerError)
335+// return
336+// }
337+// defer func() {
338+// tx.Rollback()
339+// err = s.enforcer.E.LoadPolicy()
340+// if err != nil {
341+// log.Println("failed to rollback policies")
342+// }
343+// }()
344+//
345+// // mark as registered
346+// err = db.Register(tx, domain)
347+// if err != nil {
348+// log.Println("failed to register domain", err)
349+// http.Error(w, err.Error(), http.StatusInternalServerError)
350+// return
351+// }
352+//
353+// // set permissions for this did as owner
354+// reg, err := db.RegistrationByDomain(tx, domain)
355+// if err != nil {
356+// log.Println("failed to register domain", err)
357+// http.Error(w, err.Error(), http.StatusInternalServerError)
358+// return
359+// }
360+//
361+// // add basic acls for this domain
362+// err = s.enforcer.AddKnot(domain)
363+// if err != nil {
364+// log.Println("failed to setup owner of domain", err)
365+// http.Error(w, err.Error(), http.StatusInternalServerError)
366+// return
367+// }
368+//
369+// // add this did as owner of this domain
370+// err = s.enforcer.AddKnotOwner(domain, reg.ByDid)
371+// if err != nil {
372+// log.Println("failed to setup owner of domain", err)
373+// http.Error(w, err.Error(), http.StatusInternalServerError)
374+// return
375+// }
376+//
377+// err = tx.Commit()
378+// if err != nil {
379+// log.Println("failed to commit changes", err)
380+// http.Error(w, err.Error(), http.StatusInternalServerError)
381+// return
382+// }
383+//
384+// err = s.enforcer.E.SavePolicy()
385+// if err != nil {
386+// log.Println("failed to update ACLs", err)
387+// http.Error(w, err.Error(), http.StatusInternalServerError)
388+// return
389+// }
390+//
391+// // add this knot to knotstream
392+// go s.knotstream.AddSource(
393+// context.Background(),
394+// eventconsumer.NewKnotSource(domain),
395+// )
396+//
397+// w.Write([]byte("check success"))
398+// }
399400+// func (s *State) KnotServerInfo(w http.ResponseWriter, r *http.Request) {
401+// domain := chi.URLParam(r, "domain")
402+// if domain == "" {
403+// http.Error(w, "malformed url", http.StatusBadRequest)
404+// return
405+// }
406+//
407+// user := s.oauth.GetUser(r)
408+// reg, err := db.RegistrationByDomain(s.db, domain)
409+// if err != nil {
410+// w.Write([]byte("failed to pull up registration info"))
411+// return
412+// }
413+//
414+// var members []string
415+// if reg.Registered != nil {
416+// members, err = s.enforcer.GetUserByRole("server:member", domain)
417+// if err != nil {
418+// w.Write([]byte("failed to fetch member list"))
419+// return
420+// }
421+// }
422+//
423+// var didsToResolve []string
424+// for _, m := range members {
425+// didsToResolve = append(didsToResolve, m)
426+// }
427+// didsToResolve = append(didsToResolve, reg.ByDid)
428+// resolvedIds := s.idResolver.ResolveIdents(r.Context(), didsToResolve)
429+// didHandleMap := make(map[string]string)
430+// for _, identity := range resolvedIds {
431+// if !identity.Handle.IsInvalidHandle() {
432+// didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
433+// } else {
434+// didHandleMap[identity.DID.String()] = identity.DID.String()
435+// }
436+// }
437+//
438+// ok, err := s.enforcer.IsKnotOwner(user.Did, domain)
439+// isOwner := err == nil && ok
440+//
441+// p := pages.KnotParams{
442+// LoggedInUser: user,
443+// DidHandleMap: didHandleMap,
444+// Registration: reg,
445+// Members: members,
446+// IsOwner: isOwner,
447+// }
448+//
449+// s.pages.Knot(w, p)
450+// }
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000451452// get knots registered by this user
453+// func (s *State) Knots(w http.ResponseWriter, r *http.Request) {
454+// // for now, this is just pubkeys
455+// user := s.oauth.GetUser(r)
456+// registrations, err := db.RegistrationsByDid(s.db, user.Did)
457+// if err != nil {
458+// log.Println(err)
459+// }
460+//
461+// s.pages.Knots(w, pages.KnotsParams{
462+// LoggedInUser: user,
463+// Registrations: registrations,
464+// })
465+// }
466467// list members of domain, requires auth and requires owner status
468+// func (s *State) ListMembers(w http.ResponseWriter, r *http.Request) {
469+// domain := chi.URLParam(r, "domain")
470+// if domain == "" {
471+// http.Error(w, "malformed url", http.StatusBadRequest)
472+// return
473+// }
474+//
475+// // list all members for this domain
476+// memberDids, err := s.enforcer.GetUserByRole("server:member", domain)
477+// if err != nil {
478+// w.Write([]byte("failed to fetch member list"))
479+// return
480+// }
481+//
482+// w.Write([]byte(strings.Join(memberDids, "\n")))
483+// return
484+// }
485486// add member to domain, requires auth and requires invite access
487+// func (s *State) AddMember(w http.ResponseWriter, r *http.Request) {
488+// domain := chi.URLParam(r, "domain")
489+// if domain == "" {
490+// http.Error(w, "malformed url", http.StatusBadRequest)
491+// return
492+// }
493+//
494+// subjectIdentifier := r.FormValue("subject")
495+// if subjectIdentifier == "" {
496+// http.Error(w, "malformed form", http.StatusBadRequest)
497+// return
498+// }
499+//
500+// subjectIdentity, err := s.idResolver.ResolveIdent(r.Context(), subjectIdentifier)
501+// if err != nil {
502+// w.Write([]byte("failed to resolve member did to a handle"))
503+// return
504+// }
505+// log.Printf("adding %s to %s\n", subjectIdentity.Handle.String(), domain)
506+//
507+// // announce this relation into the firehose, store into owners' pds
508+// client, err := s.oauth.AuthorizedClient(r)
509+// if err != nil {
510+// http.Error(w, "failed to authorize client", http.StatusInternalServerError)
511+// return
512+// }
513+// currentUser := s.oauth.GetUser(r)
514+// createdAt := time.Now().Format(time.RFC3339)
515+// resp, err := client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
516+// Collection: tangled.KnotMemberNSID,
517+// Repo: currentUser.Did,
518+// Rkey: appview.TID(),
519+// Record: &lexutil.LexiconTypeDecoder{
520+// Val: &tangled.KnotMember{
521+// Subject: subjectIdentity.DID.String(),
522+// Domain: domain,
523+// CreatedAt: createdAt,
524+// }},
525+// })
526+//
527+// // invalid record
528+// if err != nil {
529+// log.Printf("failed to create record: %s", err)
530+// return
531+// }
532+// log.Println("created atproto record: ", resp.Uri)
533+//
534+// secret, err := db.GetRegistrationKey(s.db, domain)
535+// if err != nil {
536+// log.Printf("no key found for domain %s: %s\n", domain, err)
537+// return
538+// }
539+//
540+// ksClient, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev)
541+// if err != nil {
542+// log.Println("failed to create client to ", domain)
543+// return
544+// }
545+//
546+// ksResp, err := ksClient.AddMember(subjectIdentity.DID.String())
547+// if err != nil {
548+// log.Printf("failed to make request to %s: %s", domain, err)
549+// return
550+// }
551+//
552+// if ksResp.StatusCode != http.StatusNoContent {
553+// w.Write([]byte(fmt.Sprint("knotserver failed to add member: ", err)))
554+// return
555+// }
556+//
557+// err = s.enforcer.AddKnotMember(domain, subjectIdentity.DID.String())
558+// if err != nil {
559+// w.Write([]byte(fmt.Sprint("failed to add member: ", err)))
560+// return
561+// }
562+//
563+// w.Write([]byte(fmt.Sprint("added member: ", subjectIdentity.Handle.String())))
564+// }
565566+// func (s *State) RemoveMember(w http.ResponseWriter, r *http.Request) {
567+// }
000000000000000000000000000000000000000000000000000000000000000000000000568569func validateRepoName(name string) error {
570 // check for path traversal attempts
+8-6
appview/state/userutil/userutil.go
···5 "strings"
6)
7000008func IsHandleNoAt(s string) bool {
9 // ref: https://atproto.com/specs/handle
10- re := regexp.MustCompile(`^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`)
11- return re.MatchString(s)
12}
1314func UnflattenDid(s string) string {
···29 // Reconstruct as a standard DID format using Replace
30 // Example: "did-plc-xyz-abc" becomes "did:plc:xyz-abc"
31 reconstructed := strings.Replace(s, "-", ":", 2)
32- re := regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`)
3334- return re.MatchString(reconstructed)
35}
3637// FlattenDid converts a DID to a flattened format.
···4647// IsDid checks if the given string is a standard DID.
48func IsDid(s string) bool {
49- re := regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`)
50- return re.MatchString(s)
51}
···5 "strings"
6)
78+var (
9+ handleRegex = regexp.MustCompile(`^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`)
10+ didRegex = regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`)
11+)
12+13func IsHandleNoAt(s string) bool {
14 // ref: https://atproto.com/specs/handle
15+ return handleRegex.MatchString(s)
016}
1718func UnflattenDid(s string) string {
···33 // Reconstruct as a standard DID format using Replace
34 // Example: "did-plc-xyz-abc" becomes "did:plc:xyz-abc"
35 reconstructed := strings.Replace(s, "-", ":", 2)
03637+ return didRegex.MatchString(reconstructed)
38}
3940// FlattenDid converts a DID to a flattened format.
···4950// IsDid checks if the given string is a standard DID.
51func IsDid(s string) bool {
52+ return didRegex.MatchString(s)
053}